Setup
This project contained three phases, which are representative of a
typical data science project:
Simulating data;
Writing and running various algorithms and recording execution
time and memory usage;
Analyzing and presenting the data.
Simulation
I simulated a linear regression dataset in R with \(n=10^6\) and \(p=10\) as follows: \[\begin{align*}
\beta_j &\overset{iid}{\sim} \mathrm{Unif}(0,10), \quad j=1,\dots, p
\\
x_{ij} &\overset{iid}{\sim} \mathcal{N}(0,1), \quad i=1, \dots,n \\
y_i &\overset{indep.}{\sim} \mathcal{N}(100+\sum_{j=1}^{p} \beta_j
x_{ij},1).
\end{align*}\] The \(y\) vector
was used by the loop and bootstrap algorithms, and the full \((y,X)\) dataset was used by the linear
regression and Markov chain Monte Carlo algorithms.
I also generated a vector \(f\) as
follows: \[\begin{align*}
I_i &\overset{iid}{\sim} \mathrm{Bern}(1/2), \quad i=1, \dots, n\\
f_i &= (-1)^{I_i}
\end{align*}\] and the datset \((f,
X)\) was used by the support vector machine algorithm.
Two \(1000\times 1000\) matrices
were populated from a standard Normal distribution. These matrices were
used in the matrix multiplication and inversion operations. The data
\((y,X,f)\) was saved as a matrix in
the file Data/data.csv, and the two matrices \(A\) and \(B\) were saved in the files Data/A.csv and
Data/B.csv. The script used to generate the data is available in the
Github repository (see reproducibility) at
Data/data.R, and the csv files for the matrices is available in the same
repository while the data.csv file was too large to be uploaded to
Github but the same data I used can be generated through the fixed seed
set in the R file.
The R code used to generate the data is very straightforward.
set.seed(32)
# Generate linear regression data
betas <- runif(10, -10, 10)
X <- matrix(rnorm(1e6 * 10), ncol = 10)
y <- 100 + X[,1:10] %*% betas + rnorm(1e6)
f <- sample(c(-1, 1), 1e6, replace = TRUE)
data <- cbind(y, X, f)
write.csv(data, "Data/data.csv")
# Generate matrix data
A <- matrix(runif(1e6), ncol = 1e3)
B <- matrix(runif(1e6), ncol = 1e3)
write.csv(A, "Data/A.csv")
write.csv(B, "Data/B.csv")
Algorithms
For each algorithm, I measure execution time and memory using the
“benchmark” package in R and the “time” and “memory_profiler” libraries
in Python. The measured time was total user time rather than processor
time. The algorithms were meant to be written in a way that is as
structurally similar as possible in both languages, but the
implementations of base functions vary greatly between the two
languages. Ultimately, this is what makes the comparison interesting
since there can be great differences in execution time for similar
operations, but it also means that an algorithm can be written in a
poorly optimized way in a language and artificially seem much faster in
the other. For instance, people often criticize the speed of loops in R
and Python and my tests agree with this criticism, but these operations
can almost always be made much faster by using vectorized
operations.
Memory turned out difficult to measure, and I relied on external
packages. R and Python implement memory management in very different
ways. For instance, R distinguishes between memory stored by vectors and
by everything else. It appears that memory usage was too low to be
recorded in R for several operations such as loops and was recorded as
0. This is a floating point problem that I was not able to solve despite
trying many approaches, including the base R implementation of memory
monitoring and several packages. The interpretation of memory usage is
also murky, as detailed in the Dicussion
section.
The following algorithms were tested: a sum loop, a geometric mean
loop and a vectorized implementation, matrix multiplication and
inversion, linear regression, the bootstrap, two Markov Chain Monte
Carlo (MCMC) algorithms, and a support vector machine (SVM). When
possible, I implemented a simple version of these algorithms using only
base functions in the language as well as a popular package/library. For
instance, for linear regression I use the lm() function in R and the
Scikit-Learn library in Python as well as an algorithm using only matrix
multiplication and inversion. Of course, these two matrix operations are
themselves algorithms with many different possible implementations. I
mainly use R in my everyday life so I am likely more familiar with the
most optimized packages in R rather than Python.
I varied the sample size when running the algorithms by selecting a
subset of these datasets. I varied the sample size from \(n=10^2\) to \(n=10^6\) by magnitude of 10 for the loop
and linear regression algorithms. I varied the size of the matrices from
\(n=10\) to \(n=10^3\) by magnitude of \(10^{1/2}\). I kept the sample size of the
bootstrap and MCMC algorithms fixed at \(n=10^3\) and varied the sampling/resampling
size (see the precise definition of the algorithms in the Results section) from \(B=10\) to \(10^5\) by magnitude of 10. For the SVM
algorithm, I varied the sample size from \(n=10\) to \(n=10^3\) by magnitude of \(10^{1/2}\). The sample sizes were chosen to
represent the shift from the typical size of a statistical analysis
around \(n=100\) to the big data size
of \(n=10^6\) which approaches the
limits of my machine for many algorithms. The sample size was varied at
lower rates for some algorithms because execution time increased too
rapidly for the bootstrap, Markov chain Monte Carlo algorithms, and the
support vector machine.
I ran scripts in R and Python on these simulated datasets for 10
iterations and for the varying sample sizes mentioned above, and I
recorded the median execution time and memory use in CSV files in
Results/results_R.csv and Results/results_python.csv. The scripts used
to run the algorithms are available in the GitHub repository at
Scripts/algorithms.py and Scripts/algorithms.R.
Analysis
I used R Markdown to generate this HTML webpage and used the plotly
package to generate interactive plots of execution time and memory use
for each algorithm. I also discuss theoretical Big-O complexity and fit
an empirical curve to the plots based on the most simple version of
these algorithms.
Reproducibility
Details on reproducing the entire project can be found at https://github.com/leovanciu/cs32_final_project. The
code for this Rmarkdown file can be downloaded by clicking on the code
button at the top of this webpage or in the GitHub repository of the
webpage at https://github.com/leovanciu/leovanciu.github.io. All
the scripts for simulating the data and running the scripts are
available in the GitHub repository cs32_final_project, and all the code
used to generate my website which was forked from
mmistakes/minimal-mistakes is available in the Github repository
leovanciu.github.io.
I am using R version 4.3.1 with RStudio version 2023.09.0+463 and
Python version 3.12.3 with Visual Studio code version 1.88.1. My
computer is a MacBook Pro (13-inch, M1, 2020) with 8 GB of memory and
running on macOS Monterey Version 12.2.1.
Results
Data
A histogram of the simulated data for \(n=10^5\) is plotted below. It has mean 100
and standard deviation 17. By construction, it is Normally
distributed.
library(plotly)
data <- read.csv("Data/data.csv")
hist <- plot_ly(x = data[1:1e5,2], type = "histogram") %>%
layout(title = "Simulated Data",
yaxis = list(title = "Frequency"),
xaxis = list(title = "y"))
hist
Loop operations
For loops are ubiquitous programming structures in data science, and
they are somewhat infamous in both R and Python, as they are often said
to be much slower than in low-level languages like C.
Loop sums
Here is pseudo code of a simple foreach loop to compute a sum.
Function loop_sum(n)
sum <- 0
For i from 1 to n
sum <- sum + i
End For
Return sum
End Function
In terms of computational complexity, the initialization step is
\(O(1)\), a linear operation like a sum
is also \(O(1)\), and by definition the
loop is \(O(n)\) which is the overall
complexity of the algorithm.
The following code shows the implementation in Python.
def loop_sum(n):
sum = 0
for i in range(n):
sum += 1
return sum
The following code shows the implementation in R.
loop_sum <- function(n) {
sum <- 0
for (i in 1:n) {
sum <- sum + 1
}
return(sum)
}
Note that the syntax for writing functions is slightly different in R
and Python. Both languages use foreach loops written similarly. R
supports both <- and = as assignement operators. Historically, comes
from the S language which also uses the <- assignment operator.
I include below the code used to generate interactive plots of
execution time and memory usage with the plotly library in R. Generating
this plot required quite a bit of data manipulation, which was easily
done in base R, and more complex operations can be done with simple
syntax with the dplyr package, which I used when storing the results
from the benchmarks. All other plots were generated with similar code
which I do not show for readability.
library(plotly)
# Read data
R <- read.csv("Results/Results_R.csv")
python <- read.csv("Results/Results_python.csv")
merged_data <- merge(R, python, by=c("Algorithm", "n"), suffixes = c("R", "python"))
algorithm_names <- c('loop_sum','loop_geom_mean', 'vectorized_geom_mean', 'matrix_multiplication',
'matrix_inversion', 'linear_regression_package', 'linear_regression_base',
'bootstrap_package', 'bootstrap_base', 'svm_package', 'svm_base',
'Metropolis_Hastings', 'MCMC_stan')
# Compute Big-O complexity
algos <- c("loop_sum") # We only plot one algorithm here but in other plots I have multiple algorithms
n_values <- seq(2, 6, by = 0.1)
O_n <- 10^n_values/10^4
O_n_python_1 <- O_n*python[python$Algorithm == algos[1],]$Time[3] # The pre-factors are standardized on the empirical time with n=10^4
O_n_R_1 <- O_n*R[R$Algorithm == algos[1],]$Time[3]
# Time plot
plot_ly() %>%
add_trace(data = R[R$Algorithm == algos[1],], x = ~log(n, 10), y = ~Time,
type = 'scatter', mode = 'lines+markers', name = 'R',
line = list(color = "blue", dash = 'solid'),
marker = list(color = "blue", symbol = 'circle')) %>%
add_trace(data = python[python$Algorithm == algos[1],], x = ~log(n, 10), y = ~Time,
type = 'scatter', mode = 'lines+markers', name = 'loop_sum Python',
line = list(color = "red", dash = 'solid'),
marker = list(color = "red", symbol = 'dot')) %>%
add_trace(x = n_values, y = O_n_python_1, type = 'scatter', mode = 'lines',
name = 'O(n)', line = list(color = 'black', dash = 'dash')) %>%
add_trace(x = n_values, y = O_n_R_1, type = 'scatter', mode = 'lines',
name = 'O(n)', line = list(color = 'black', dash = 'dash'), showlegend = FALSE) %>%
layout(title = "Execution Time",
xaxis = list(title = "log(n)", tickmode = "array", tickvals = 0:6, ticktext = as.character(0:6)),
yaxis = list(title = "Time (s)"),
legend = list(title = "Legend", orientation = "h", x = 0.3, y = -0.2))
We see that loops in R run two to three times faster than in Pyton.
We see that \(O(n)\) fits the data
perfectly. It seems that memory usage was too low to be recorded in R
and there is no clear trend on the memory usage in Python, although it
Python does use a strangely large amount of memory for such a simple for
loop algorithm. This is a win for execution time for R, but it is
well-known that loops are slow in both R and Python due to the many
abstractions made by the programming languages. It is often suggested to
vectorize operations, which can speed up loops considerably.
Vectorized
operations
Vectorized operations are operations applied to every entry of an
array. They are still loops, but they are typically compiled directly in
C which is much faster than executing a loop in R or Python. The
computational complexity theoretically should still be the same as a
loop since the same operations are performed, but the pre-factor is
reduced. In addition, the operations can be optimized to perform
parallel operations on the data, so it is also possible for the order of
execution time to be reduced as well. In the following plots, I compare
the computation of the geometric mean \[\left(\prod_{i=1}^n y_i \right)^{1/n}\]
with a loop algorithm and with a vectorized operation. It is convenient
to work on a log scale to avoid overflow issues, so I compute the
equivalent form \[\exp\left(\frac{1}{n}
\sum_{i=1}^n \log y_i\right).\]
R has a distinct vector object type, while in Python they are simply
one-dimensional arrays. Vectorized operations are implemented in Python
through libraries such as numpy and they are implemented natively in R
either through implicit notation as in python or with a call to the
function apply. There are also several packages in R implementing
efficient vectorized operations. Here is the code in Python:
def vectorized_geom_mean(data):
return np.exp(np.sum(np.log(data)) / len(data))
and in R:
vectorized_geom_mean <- function(data) {
return(exp(mean(log(data))))
}
Here are plots showing execution time and memory for plots and
vectorized operations in R and Python.
We see that loops in Python and R are very slow compared to the
vectorized operation. Although the pre-factor is much smaller for
vectorized operations, the computational complexity is still \(O(n)\), as can be seen by fitting a linear
curve to the plot of vectorized operations below.
Matrix
operations
Matrix operations, namely addition, subtraction, multiplication,
inversion, and dot products are central to statistics particularly for
multivariate data. For instance, linear regression can be framed
entirely as an algorithm involving only matrix multiplication and
inversion. Here, I test the implementation of matrix multiplication and
inversion in Python and R. This is a one-liner in both languages, but
the actual algorithms for matrix multiplication and inversion are
actually quite computationally costly.
For matrix multiplication, the mathematical definition gives the
following algorithm.
Function mutiply_matrices(A, B)
// Dimensions
m <- number of rows in A
n <- number of columns in A
p <- number of columns in B
C <- Initialize with zeros
For i from 1 to m
For j from 1 to p
sum <- 0
For k from 1 to n
sum <- sum + A[i][k] * B[k][j]
EndFor
C[i][j] <- sum
EndFor
EndFor
Return C
End Function
For each pair of \(m\) row from
\(A\) and \(p\) column from \(B\), \(n\)
multiplications are performed through three nested loops. For each of
the \(n\) multiplications, there is a
sum which has complexity \(O(1)\). This
gives computational complexity \(\mathcal{O}(mnp)\) and in the case where
\(m=n=p\) this is \(O(n^3)\). However, Python and R likely have
a more efficient version through some clever algorithm.
Here is basic pseudocode for the Gauss-Jordan algorithm for matrix
inversion.
Function invert_matrix(A)
// Dimensions
n <- number of rows in A
B <- Initialize matrix B as an identity matrix
// Perform Gaussian elimination
For i from 1 to n
divisor <- A[i][i]
For j from 1 to n
A[i][j] <- A[i][j] / divisor
B[i][j] <- B[i][j] / divisor
EndFor
// Set other elements in column i of A to 0
For k from 1 to n
If k != i
factor <- A[k][i]
For j from 1 to n
A[k][j] <- A[k][j] - factor * A[i][j]
B[k][j] <- B[k][j] - factor * B[i][j]
EndFor
EndIf
EndFor
EndFor
Return B
End Function
Likewise to matrix multiplication, there are three nested loops which
gives complexity \(\mathcal{O}(n^3)\).
In Python this is simply implemented as follows.
def multiply_matrices(A, B):
return A @ B
def invert_matrix(A):
return np.linalg.inv(A)
Likewise in R this is simply implemented as follows. Sidenote: I
really do not like the syntax %*% for matrix multiplication in R.
multiply_matrices <- function(A, B) {
return(A %*% B)
}
invert_matrix <- function(A) {
return(solve(A))
}
Interestingly, the implementation of matrix multiplication and
inversion in Python seems to scale linearly or even better with \(n\), but it blows up to order \(O(n^3)\) for R for larger matrices. For
smaller matrices of size less than \(100
\times 100\), R seems to be faster while Python is much faster
for large \(n=1000\). An important note
is that memory also seems to increase linearly for larger matrices in
Python which may also explain why the execution time is much better than
the theoretical complexity in python.
Linear
regression
The ordinary least squares (OLS) estimate of linear regression \(\hat{\beta}\) for \(\beta\) is the vector that solves the
constraint \[\arg \min_\beta (y - X
\beta^T)^2.\] If the data is Normally distributed as in the simulation, then it is well-known that \(\hat{\beta}\) is given by \[\hat{\beta} = (X^T X)^{-1} X^T y.\]
Therefore, OLS consists simply of matrix multiplication and inversion.
In pseudocode, it can be implemented in the following manner.
Function linear_regression(X, y)
// Dimensions
n <- length of data y
p <- number of columns in X
X_b <- Append column 1 to X // Include an intercept
// Compute coefficients
beta_hat <- (X_b.T @ X_b)^-1 @ (X_b.T @ y)
Return beta_hat
End Function
The matrix augmentation operation is \(O(n)\) since we are appending \(n\) rows. The matrix multiplication and
inversion are the dominating steps of this algorithm. As explained in
the previous section on matrices, the matrix multiplication X_b.T @ X_b
step is \(O(n \times (p+1)^2)\) while
the matrix inversion is \(O((p+1)^3)\),
although these matrix operations are likely implemented more efficiently
in Python and R. The last step of multiplying the matrix (X_b.T @
X_b)^-1 with the vector (X_b.T @ y) is \(O((p+1)^2)\). Therefore, the overall
complexity is \(O(p^3)\) or \(O(n \times (p+1)^2)\) depending on whether
\(p<<n\). We can never have \(p>n\) in linear regression since the
coefficients would become unidentifiable. In this case since we keep
\(p=10\) and we increase \(n\) to \(n=10^6\), the dominating step is the first
matrix multiplication step which is \(O(n)\). This setup is typical of classical
regression where a relatively small number of covariates is used, but in
a high-dimensional setting we could have large \(p\) which would make the matrix inversion
step more costly.
In Python this can be implemented like the algorithm above directly
using matrix operations or using the Scikit-Learn library:
# Linear regression from base
def lin_reg_base(X, y):
Xb = np.hstack([np.ones((X.shape[0], 1)), X])
beta_hat = np.linalg.inv(Xb.T @ Xb) @ (Xb.T @ y)
return beta_hat
# sklearn-Learn linear regression
def lin_reg_sklearn(X, y):
model = LinearRegression()
model.fit(X, y)
beta_hat = np.hstack([model.intercept_, model.coef_])
return beta_hat
and in R this is also implemented in base R using the lm()
function:
# Linear regression from base
lin_reg_base <- function(X, y) {
Xb <- cbind(1, X)
beta_hat <- solve(t(Xb) %*% Xb) %*% (t(Xb) %*% y)
return(betas)
}
# Linear regression using lm()
lin_reg_package <- function(X, y) {
model <- lm(y ~ X + 0)
return(coef(model))
}
Linear regression seems to scale linearly for execution time in R,
but execution time seems constant and even perplexingly decreases for
large \(n\) in Python. The
implementation from scratch in both Python and R was faster than the
implementation from sklearn in Python and from the base function in R,
and R was faster for data sets less than \(n=10^5\) while Python scaled better for
large \(n=10^6\). The execution time of
linear regression from sklearn blows up at \(10^6\) and is considerably slower than all
other implementations. This makes sense since scikit-learn and lm()
contain additional steps than my simple code, such as translating the
formula syntax into matrix calculations and many other options for more
complicated versions of linear regression, although I am not sure why it
scales so poorly for large \(n\).
Interestingly, memory scaled roughly linearly and all algorithms in both
R and Python had similar memory curves, which may explain why execution
time did not seem to increase in the basic implemention in Python.
Bootstrap
The bootstrap algorithm is popular in statistics because it allows
for the estimation of the properties of general estimators under minimal
assumptions even if they are not analytically tractable. The idea of the
bootstrap is to resample from the data to create new synthethic datasets
and compute a new estimator for each dataset, from which the sampling
distribution of the estimator can be estimated. The bootstrap was
influential in moving statistics away from mathematics and towards
computation since one does not need to know anything about the
distribution of the estimator to perform the algorithm. The major
assumptions of the standard bootstrap are that the data are independent
and identically distributed and that the empirical cumulative density
function (CDF) is close to the true CDF of the data. The latter is
asymptotically justified but empirically untestable.
Here is a pseudocode for the bootstrap algorithm.
Function bootstrap(data, statistic, B, alpha)
n <- length of data
idx <- sample B sets of indices with replacement
samples <- extract data with indices idx
stat <- sort(statistic(samples))
lower_CI <- extract (100*(1-alpha)/2)th sample
upper_CI <- extract (100*(1-(1-alpha)/2)th sample
Return lower_CI, upper_CI
End Function
The idx resampling step is \(O(n \times
B)\) and extracting the samples is also \(O(n \times B)\). For a linear statistic
like a sample mean, the statistic step is also \(O(n \times B)\). The sorting step depends
on the algorithm used, but the one used in Python is Timsort which has
\(O(B \log (B))\). After that,
computing the two sample quantiles on the sorted data is \(O(1)\). However, a potentially more
efficient way of doing this step is using an implementation for sample
quantiles which may avoid sorting the entire dataset. Therefore, since
we increase \(B\) and keep \(n=10^3\) fixed, the dominating step is
likely the resampling/extracting/statistic step, which has complexity
\(O(B)\), although if we use a naive
sorting algorithm like bubble sort this may become costly. There are
other more complicated implementations of the bootstrap that do not use
sample quantiles for the confidence interval. A popular approach is to
use a bootstrapped pivot within each iteration of the algorithm, thereby
nesting an additional loop in the algorithm which increases the
complexity to \(O(B^2)\).
In Python the standard bootstrap can be implemented using the
algorithm above with vectorized operations for efficiency, as well as
using the scipy.stats library as follows.
# Bootstrap from base
def bootstrap_base(data, statistic, B):
n = len(data)
idx = np.random.randint(0, n, (B, n))
samples = data[idx]
stat = statistic(samples)
confidence_interval = np.quantile(stat, [0.025, 0.975])
return confidence_interval
# scipy.stats bootstrap
def bootstrap_scipy(data, statistic, B, confidence_level=0.95):
res = stats.bootstrap((data,), statistic, n_resamples=B, confidence_level=confidence_level, method='percentile')
return res.confidence_interval
def sample_mean(data):
return sum(data)/len(data)
In R this can also be implemented using a vectorized version or using
the boot package.
# Bootstrap from base
bootstrap_base <- function(data, statistic, B) {
n <- length(data)
results <- replicate(B, statistic(sample(data, size = length(data), replace = TRUE)))
ci <- quantile(results, probs = c(0.025, 0.975))
return(ci)
}
mean_function <- function(data) {
mean(data)
}
# Bootstrap from boot
bootstrap_package <- function(data, statistic, R) {
result <- boot(data, statistic = statistic, R = R)
ci <- boot.ci(result, type = "perc")$percent[4:5]
return(ci)
}
mean_boot <- function(data, indices) {
mean(data[indices])
}
In the plots above, we see that the execution time for both the
package and base implementations of the bootstrap in Python blow up for
large \(B=10^5\). Since the plots are
interactive, I encourage you to remove the Python data points by cliking
on them in the legend. Then we can see that the implementations in R
scale linearly as expected from the theoretical complexity. We also see
that memory increases extremely fast in both R and Python, reaching 4GB
for \(B=10^5\) in Python and 2GB in R.
Running \(10^5\) iterations is likely
overkill in practice, particularly for a sample mean which has rapid
convergence by the law of large numbers. In general, the Monte Carlo
error for a simple estimator often becomes much smaller then the
sampling variance above \(10^3\) or
\(10^4\) iterations.
MCMC
Markov chain Monte Carlo algorithms (MCMC) are central methods to
Bayesian statistics since they allow us to estimate the posterior
distribution of parameters with general priors and likelihoods. Bayes
theorem shows how to obtain a posterior distribution by multiplying the
prior with the likelihood and dividing by the marginal distribution
which involves taking an integral. In a few cases like the Normal
distribution, the posterior distribution is of the same family as the
prior and a closed form is available. However, for arbitrary priors and
likelihoods this integral is typically very difficult or intractable,
and numerical methods for computing integrals on a grid suffer from the
curse of dimensionality. MCMC algorithms such as the Metropolis-Hastings
algorithm approximates the posterior distribution by iteratively
generating samples from a proposal distribution and computing an
acceptance ratio that allows the sample to approach the posterior
distribution. The acceptance ratio depends only on the current and
previous sample, and it avoids having to compute the normalization
constant since it cancels out in the ratio. A simple proposal function
uses the Gaussian distribution at the current point, while more other
MCMC algorithms like Hamiltonian Monte Carlo use more complicated
proposal distributions by leveraging Hamiltonian dynamics. MCMC
algorithms are well justified theoretically, and unlike the bootstrap it
is possible to assess the convergence of the algorithm by running
multiple chains and examining whether the chains converge to the same
distribution, although I do not consider this here.
Here is a simple pseudocode implementation of the Metropolis Hastings
algorithm for the same linear regression setting as before. Linear
regression is actually conjugate with Normal distributions which I use
here for simplicity, so Metropolis-Hastings or even any approximation is
not strictly necessary in this setting.
Function metropolis_hastings(X, y, B, beta_0, proposal_sd, sigma)
// Include an intercept
X_b <- Append column 1 to X
// Initialize parameters
current_beta <- beta_0
samples <- current_beta
Xb <- X_b dot current_beta
current_likelihood <- sum(log(N(y|Xb,sigma))
current_prior <- sum(log(N(current_beta|mu0,sigma0)))
// Sampling
For i from 1 to B
// Proposal
proposed_beta <- sample N(current_beta,proposal_sd)
Xb_proposed <- X_b dot proposed_beta
proposed_likelihood <- sum(log(N(y|Xb_proposed,sigma))
proposed_prior <- sum(log(N(proposed_beta|mu0,sigma0)))
// Accept/reject new beta
p_accept <- exp(proposed_likelihood + proposed_prior - current_likelihood - current_prior)
U <- sample Unif(0,1)
If U < p_accept
current_beta <- proposed_beta
current_likelihood <- proposed_likelihood
current_prior <- proposed_prior
Append current_beta to samples
Return samples
End Function
Augmenting the matrix is \(O(n)\),
the matrix dot products from linear regression is \(O(n \times p)\), computing the
log-likelihood is \(O(n)\) and
computing the prior is \(O(p)\).
Therefore, running the loop for B iterations has complexity \(O(n \times p \times B)\), or keeping all
else fixed it is \(O(B)\). This seems
great since we seem to have avoided curse of dimensionality, but it
hides the important fact that this algorithm must be run until it
converges to the posterior distribution. This is actually highly
dependent on dimension because the random walk will need to run much
longer in high dimensions to explore the entire space of the posterior
distribution. Some algorithms like Hamiltonian Monte Carlo implemented
in Stan in both R and Python help address this issue though more
complicated proposal functions that reduce the autocorrelation between
proposals.
The Metropolis-Hastings algorithm can be implemented in Python as
follows.
# Metropolis_hastings from scratch
def metropolis_hastings(X, y, num_samples, beta_0=np.zeros(11), proposal_sd=1, sigma=1):
X = np.hstack([np.ones((X.shape[0], 1)), X])
current_beta = beta_0
samples = [current_beta]
Xb = X.dot(current_beta)
current_likelihood = np.sum(stats.norm.logpdf(y, Xb, sigma))
current_prior = np.sum(stats.norm.logpdf(current_beta, 0, 10))
for i in range(num_samples):
proposed_beta = np.random.normal(current_beta, proposal_sd)
Xb_proposed = X.dot(proposed_beta)
proposed_likelihood = np.sum(stats.norm.logpdf(y, Xb_proposed, sigma))
proposed_prior = np.sum(stats.norm.logpdf(proposed_beta, 0, 10))
p_accept = np.exp((proposed_likelihood + proposed_prior) - (current_likelihood + current_prior))
if np.random.rand() < p_accept:
current_beta = proposed_beta
current_likelihood = proposed_likelihood
current_prior = proposed_prior
samples.append(current_beta)
return np.array(samples)
The Metropolis-Hastings algorithm can be implemented in a similar
form in R as follows.
# Metropolis_hastings for linear regression from base R
metropolis_hastings <- function(X, y, num_samples, beta_0 = rep(0,11), proposal_sd=1, sigma=1) {
X <- cbind(1, X)
current_beta <- beta_0
samples <- list(current_beta)
Xb <- X %*% current_beta
current_likelihood <- sum(dnorm(y, Xb, sigma, log = TRUE))
current_prior <- sum(dnorm(current_beta, 0, 10, log = TRUE))
for (i in 1:num_samples) {
proposed_beta <- mvrnorm(1, current_beta, diag(rep(proposal_sd, length(beta_0))))
Xb_proposed <- X %*% proposed_beta
proposed_likelihood <- sum(dnorm(y, Xb_proposed, sigma, log = TRUE))
proposed_prior <- sum(dnorm(proposed_beta, 0, 10, log = TRUE))
p_accept <- exp((proposed_likelihood + proposed_prior) - (current_likelihood + current_prior))
if (runif(1) < p_accept) {
current_beta <- proposed_beta
current_likelihood <- proposed_likelihood
current_prior <- proposed_prior
}
samples[[i + 1]] <- current_beta
}
return(do.call(rbind, samples))
}
Both R and Python also support interfaces for the Stan programming
language using cmdstanpy in Python and Rstan in R. Stan performs
efficient Hamiltonian Monte Carlo with a No-U-Turn sampler. The notation
in Stan is probabilistic which is convenient for statistical model
building, and the computations are compiled efficiently in C++. Here is
an example of a non-conjugate implementation of linear regression with a
Cauchy prior on the standard deviation.
data {
int N; // Sample size
int K; // Number of predictors
matrix[N, K] X; // Covariates
vector[N] y; // Outcome
}
parameters {
vector[K] beta;
real sigma;
}
model {
// Priors
beta ~ normal(0, 10);
sigma ~ cauchy(0, 5);
// Likelihood
y ~ normal(X * beta , sigma);
}
Both Python and R’s execution times for Metropolis-Hastings seem to
scale linearly with time as expected from the theoretical complexity,
and R generally seems to be faster than Python particularly for a large
number iterations at \(B=10^5\). In
this simple case of linear regression, running \(10^5\) iterations is definitely uncessary
and convergence was achieved way before but this may be needed for very
complex models. Stan’s execution time is very similar in both interfaces
and it seems to scale even better than linearly for large number of
iterations. Since Stan has much lower autocorrelation in the chains than
Metroplis-Hastings, Stan actually has much higher effective sample size
which makes it performance quite impressive, and \(10^5\) iterations is typically more than
necessary in Stan. We also see that memory usage blows up in R with 3GB
of memory used for \(10^5\) iterations,
but it stays surprisingly low below 100MB in Python.
SVM
I will not say much about support vector machines (SVM) because I am
not as familiar with this algorithm compared to statistical ones.
Nonetheless, I thought it would be interesting to include a machine
learning algorithm since this is often thought to be a major strength of
Python through its large ecosystem of libraries. My understanding of the
algorithm using a simple form of gradient descent is as follows.
Function svm(X, y, epochs, learning_rate, C)
// Initialize weight and bias parameters
w <- vector 0
b <- 0
For each epoch from 1 to epochs
For each sample i from 1 to length of y
Decision_value <- X[i] dot (w + b)
// Check if data is on the correct side of the margin
If y[i] * decision_value < 1 then
// Update w and b for incorrectly classified samples
w <- w + learning_rate * (y[i] * X[i] - 2 * (1/C) * w)
b <- b + learning_rate * y[i]
Else
// Update regularization for correctly classified samples
w <- w - learning_rate * (2 * (1/C) * w)
Return w, b
End Function
The initializations are \(O(p)\) and
\(O(1)\), the outer loop runs for
epochs iterations and the inner loop runs for \(n\) interations, the product X[i] dot C is
\(O(p)\), so the overall complexity is
\(O(\text{epochs} \times n \times p)\).
In this case, I am keeping epochs and p fixed and am increasing the
sample size, so the complexity is \(O(n)\).
This is implemented in Python following the pseudocode and using the
sklearn library as follows.
# SVM from base
def svm_base(X, y, epochs=100, learning_rate=0.01, C=1.0):
w = np.zeros(X.shape[1])
b = 0
for epoch in range(epochs):
for i in range(len(y)):
if y[i] * (np.dot(X[i], w) + b) < 1:
w += learning_rate * ((y[i] * X[i]) + (-2 * (1/C) * w))
b += learning_rate * y[i]
else:
w -= learning_rate * (2 * (1/C) * w)
return w, b
# sklearn-Learn SVM
def svm_sklearn(X, y):
clf = SVC(kernel='linear', C=1.0)
clf.fit(X, y)
return clf.coef_[0], clf.intercept_[0]
Likewise, this is implemented in R following a similar form and using
a package called e1071.
# Simple linear SVM from base
svm_base <- function(X, y, epochs = 100, learning_rate = 0.01, lambda = 0.01) {
n_samples <- nrow(X)
n_features <- ncol(X)
weights <- matrix(0, nrow = n_features, ncol = 1)
intercept <- 0
for (epoch in 1:epochs) {
for (i in 1:n_samples) {
if (y[i] * (crossprod(weights, X[i, ]) + intercept) < 1) {
weights <- weights + learning_rate * ((y[i] * matrix(X[i, ], ncol = 1)) - (2 * lambda * weights))
intercept <- intercept + learning_rate * y[i]
} else {
weights <- weights - learning_rate * (2 * lambda * weights)
}
}
}
list(coefficients = weights, intercept = intercept)
}
# SVM using e1071 package
svm_e1071 <- function(X, y) {
model <- svm(x = X, y = as.factor(y), kernel = "linear")
list(coefficients = t(as.vector(model$coefs) %*% model$SV), intercept = -model$rho)
}
Both the R and Python implementations from scratch seem to scale
roughly linearly with sample size and R is generally faster than Python,
but the package implementations of SVM are considerably faster. Memory
usage is again strangely very low in R and there is no discernable
trend.
Discussion
Execution time
R generally had better execution times for smaller to medium-sized
datasets across all types of operations I tested. More specifically, R
had faster loops for all sample sizes, faster vectorized operations for
small-medium sample sizes, and faster matrix operations for small-medium
sample sizes. Consistent with these results, linear regression which is
just implemented as matrix operations was faster for small-medium sample
sizes, and the bootstrap, MCMC, and SVM which are loop algorithms were
faster overall in R. This may be due to R’s efficient handling of
vectorized operations and its optimization for operations commonly used
in statistics and data manipulation.
However, Python exhibited better performance with large datasets,
particularly in vectorized operations and matrix manipulations.
Likewise, linear regression was faster in Python for large sample sizes.
This is possibly due to its efficient libraries like NumPy, which are
designed to handle large scale data efficiently.
Memory usage
In most cases, I do not think that memory usage was well-measured in
R, so I will not make overall statements about memory management.
However, the bootstrap and MCMC were costly for memory in both
languages. In addition, memory management in Python seemed to be for the
most part agnostic to the type of operations executed, using similar
memory for a simple sum loop and for the support vector machine
algorithm. In addition, Python did seem to request much more of my
computer’s memory than R, and I had to leave it running overnight to
avoid running other operations simultaneously on my computer, but I do
not think this is well-quantified. In addition, both languages are
garbage-collected language, meaning that they automatically release
memory when they check that the objects are not used, but memory at any
given time is not necessarily representative of computational
complexity.
Even if the memory usage of an operation was well-measured in R and
Python, it is not clear if it is desirable to have low or high memory
usage, since low memory usage could be due to an efficient
implementation or rather be due to poor memory attribution which ends up
slowing down the program. Hence, there is a tradeoff between execution
time and memory storage.
Other
considerations
In terms of code writing, I personally prefer R for most data science
projects because I can very quickly analzye data in a couple lines
without thinking much about the syntax. Also, I find the syntax of dplyr
very clear for data manipulation, and I think that the high quality
visualization of ggplot2 is clearly unmatched by matplotlib or any other
library in python. However, if I have to implement a more complex
operation, I often find R’s syntax frustrating since I constantly have
to look up obscure notations, and the execution is certainly much slower
than a low-level language like C. On the other hand, Python is much
better-supported for machine learning and for general purpose
programming.
In this project, a challenge for me was to store the simulated manner
that can be easily read in both Python and R. If I was using just R I
would use a .rds file containing all the data in an object with complex
structure that can be easily imported. However, to be read in Python I
had to save the data in several .csv files which were read with an
awkward structure in Python and I had to include a worrisome
errors=‘coerce’ line which solved the problem that my file included both
strings and numerics. This is mainly my fault because I do not know how
to pogram in Python well, but R makes it (dangerously) easy to use
different objects without thinking about structure too much. I find this
to be a great advantage. For the purpose of this project, I did not
really care much about importing the data smoothly since I was focused
on making the algorithms comparable. An interesting aspect that I did
not test was the speed of reading data, which I clearly don’t know
enough about to test fairly, but it can be a major consideration for
large datasets. Another thing I did not consider was parallel computing,
which can be implemented in both R and Python and can significantly
speed up computations. There are also many other programming languages
than Python and R that can be used in data science, such as Julia or on
the other end of abstraction, C/C++.
Interactive
visualization
I used plotly to make the interactive plots in this page, and it is
the first time I used interactive visualization. I did not really like
this experience because I think the plots mostly end up distracting the
user without conveying much more information. I find exploratory data
visualization much easier and beautiful in ggplot2 or even base R.
However, it also seems more interesting and eye-catching than a
black-and-white plot you would read in a paper, particularly for blog
post format like this.
I also chose to plot the data on a log scale, which is difficult to
interpret. For instance, we see that linear functions become very
distorted. However, I thought this choice was reasonable to highlight
the different needs of data science from small samples, which is my main
area of interest, to big data. As a statistician, I also would have
wished to convey uncertainty in these estimates. I used 10 iterations of
each algorithm and computed the median, but it may have been interesting
to find a way to include uncertainty on the plots, although it would
have made the plots more cluttered than they already are.
This was also my first time creating a website, which I forked from
mmistakes/minimal-mistakes. I liked this format of using markdown
notebook, which allowed to easily include text, code, and plots from
different programming languages in an aesthetically pleasing format.
However, the easiness of writing in a markdown file as opposed to HTML
and CSS files comes at the cost of customization.
Is R or Python
better?
Clearly, computational performance is highly dependent on the
situation, including the data dimension, the algorithm of interest, the
easiness of optimizing code, and the machine used to compute it.
Therefore, I cannot even answer the question of whether R or Python is
faster for my uses and my computer, let alone for anyone else. However,
in most of my everyday data science operations, my sample sizes are
below \(10^5\), and this simulation
shows me that in that context R keeps up with Python and, to my
surprise, is often better. In some cases, such as very large sample
sizes or machine learning algorithms with well-implemented libraries,
Python may be significantly faster. Overall, it does seem that R was
much faster for me, which is particularly clear through the fact that
the entire script was run in 9 minutes in R and in 126 minutes in
Python.
LS0tCnRpdGxlOiAiQ29tcGFyaXNvbiBvZiBDb21wdXRhdGlvbmFsIFBlcmZvcm1hbmNlIG9mIFIgYW5kIFB5dGhvbiBmb3IgRGF0YSBTY2llbmNlIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgdG9jX2NvbGxhcHNlZDogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRoZW1lOiBsdW1lbgotLS0KYGBge3IsIHNldHVwLCBlY2hvPUZBTFNFfQpsaWJyYXJ5KHJldGljdWxhdGUpCmtuaXRyOjpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gIi9Vc2Vycy9hbmNhdmFuY2l1cG9wZXNjdS9EZXNrdG9wL0NsYXNzZXMvQ1MgMzIvY3MzMl9maW5hbF9wcm9qZWN0LyIpCmBgYAo8YSBocmVmPSdodHRwczovL2xlb3ZhbmNpdS5naXRodWIuaW8vJyBjbGFzcz0nYnRuIGJ0bi1wcmltYXJ5Jz5CYWNrIHRvIEhvbWU8L2E+CgojIEludHJvZHVjdGlvbgoKVGhpcyBwcm9qZWN0IGNvbnNpc3RzIG9mIGEgY29tcGFyaXNvbiBvZiBleGVjdXRpb24gdGltZSBhbmQgbWVtb3J5IHVzZSBpbiBQeXRob24gYW5kIFIgZm9yIGNvbXB1dGF0aW9uYWwgb3BlcmF0aW9ucyBjb21tb24gaW4gZGF0YSBzY2llbmNlLCBuYW1lbHkgZm9yIGxvb3BzIGFuZCB2ZWN0b3JpemVkIG9wZXJhdGlvbnMsIG1hdHJpeCBtdWx0aXBsaWNhdGlvbiBhbmQgaW52ZXJzaW9uLCBhbmQgc29tZSBwb3B1bGFyIGNvbXB1dGF0aW9uYWxseSBoZWF2eSBzdGF0aXN0aWNhbCBhbmQgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zLgoKCiMjIFdoeSBSIGFuZCBQeXRob24KClIgYW5kIFB5dGhvbiBhcmUgYXJndWFibHkgdGhlIHR3byBtb3N0IHBvcHVsYXIgb3Blbi1zb3VyY2UgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UgdXNlZCBpbiBkYXRhIHNjaWVuY2UuIEJvdGggYXJlIGNvbnNpZGVyZWQgaGlnaC1sZXZlbCBwcm9ncmFtbWluZyBsYW5ndWFnZXMgd2l0aCBtYW55IGFic3RyYWN0aW9ucyBidWlsdC1pbiB0byBmZWF0dXJlIHNpbXBsZSBzeW50YXguCgpQeXRob24gaXMgYWxzbyB3aWRlbHkgb3V0c2lkZSBmb3IgcHJvZ3JhbW1pbmcgb3V0c2lkZSBkYXRhIHNjaWVuY2UgYW5kIHJhbmtzIGZpcnN0IGluIG1hbnkgcG9wdWxhcml0eSBpbmRpY2VzIGxpa2UgdGhlIFRJT0JFIGluZGV4LiBJdHMgcG9wdWxhcml0eSBpbiBkYXRhIHNjaWVuY2UgbWF5IGJlIGV4cGxhaW5lZCBieSBpdHMgZXh0ZW5zaXZlIGVjb3N5c3RlbSBvZiBvcGVuLXNvdXJjZSBsaWJyYXJpZXMgY29udHJpYnV0ZWQgYnkgdGhlIHByb2dyYW1taW5nIGNvbW11bml0eSwgc3VjaCBhcyBOdW1QeSwgcGFuZGFzLCBzY2lraXQtbGVhcm4uIFRoZXNlIGxpYnJhcmllcyBwcm92aWRlIGZsZXhpYmxlIGFsZ29yaXRobXMgZm9yIGRhdGEgbWFuaXB1bGF0aW9uLCBhbmFseXNpcywgYW5kIHZpc3VhbGl6YXRpb24uIFB5dGhvbidzIGVjb3N5c3RlbSBoYXMgbWFueSBvdGhlciBsaWJyYXJpZXMgYmV5b25kIGRhdGEgc2NpZW5jZSwgc3VjaCBhcyBmb3Igd2ViIGRldmVsb3BtZW50IGFuZCBuYXR1cmFsIGxhbmd1YWdlIHByb2Nlc3NpbmcsIGFuZCBpdCBoYXMgZXh0ZW5zaXZlIGRvY3VtZW50YXRpb24gb24gR2l0SHViLiBDb21wYXJlZCB0byBSLCBQeXRob24gaXMgbW9yZSBwb3B1bGFyIGluIHRoZSBkYXRhIHNjaWVuY2UgaW5kdXN0cnkgYW5kIGluIHRoZSBhY2FkZW1pYyBtYWNoaW5lIGxlYXJuaW5nIGNvbW11bml0eS4gUHl0aG9uIGxpYnJhcmllcyBhcmUgdHlwaWNhbGx5IGJ1aWx0IGluIEMgb3IgQysrIGZvciBlZmZpY2llbnQgaW1wbGVtZW50YXRpb25zLgoKUiBpcyBhIHByb2dyYW1taW5nIGxhbmd1YWdlIGRlc2lnbmVkIHNwZWNpZmljYWxseSBmb3Igc3RhdGlzdGljYWwgY29tcHV0aW5nIGFuZCBncmFwaGljcy4gTGlrZSBQeXRob24sIFIncyBtYWluIHN0cmVuZ3RoIGlzIGl0cyBleHRlbnNpdmUgY29sbGVjdGlvbiBvZiBvcGVuLXNvdXJjZSBwYWNrYWdlcyBjb250cmlidXRlZCBieSB0aGUgY29tbXVuaXR5IG9mIHVzZXJzLCB3aGljaCBtYWlubHkgY29uc2lzdHMgb2Ygc3RhdGlzdGljaWFucyBhbmQgcmVzZWFyY2hlcnMuIEl0IGZlYXR1cmVzIGJ1aWx0LWluIGZ1bmN0aW9ucyBmb3IgcG9wdWxhciBzdGF0aXN0aWNhbCBhbGdvcml0aG1zIGxpa2UgbGluZWFyIHJlZ3Jlc3Npb24gYXMgd2VsbCBhcyBmbGV4aWJsZSBncmFwaGljYWwgY2FwYWJpbGl0aWVzLiBUaGVyZSBhcmUgYWxzbyBtYW55IHBhY2thZ2VzIGZvciBjb21wbGV4IGRhdGEgbWFuaXB1bGF0aW9uIGxpa2UgdGhlIFRpZHl2ZXJzZSBjb2xsZWN0aW9uIHdoaWNoIGluY2x1ZGVzIGRwbHlyIGFuZCB0aGUgaGlnaC1xdWFsaXR5IHZpc3VhbGl6YXRpb24gdG9vbCBnZ3Bsb3QyLiBSJ3Mgc3ludGF4IGlzIHRhaWxvcmVkIGZvciBzdGF0aXN0aWNhbCBhbmFseXNpcywgdXNpbmcgdmVjdG9ycyBhcyBpdHMgZGF0YSBzdHJ1Y3R1cmUgZm9yIHN0b3JpbmcgYW5kIG1hbmlwdWxhdGluZyBkYXRhLiBJdCBpbmNsdWRlcyBtYW55IGJ1aWx0LWluIHJlcHJvZHVjaWJpbGl0eSBmZWF0dXJlcyBsaWtlIHNldHRpbmcgYSBzZWVkIGZvciBwc2V1ZG8tcmFuZG9tIG51bWJlciBnZW5lcmF0b3JzLiBJdHMgcGFja2FnZXMgYXJlIGFsc28gdHlwaWNhbGx5IGltcGxlbWVudGVkIGluIEMgb3IgQysrLgoKCk90aGVyIHBvcHVsYXIgaGlnaC1sZXZlbCBwcm9ncmFtbWluZyBsYW5ndWFnZXMgaW4gZGF0YSBzY2llbmNlIGluY2x1ZGUgSmF2YSBhbmQgcmVsYXRpdmUgbmV3Y29tZXIgSnVsaWEsIGFuZCBjb21tb24gcHJvcHJpZXRhcnkgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2VzIGluY2x1ZGUgU0FTLCBTdGF0YSwgYW5kIE1BVExBQi4KCiMgU2V0dXAKClRoaXMgcHJvamVjdCBjb250YWluZWQgdGhyZWUgcGhhc2VzLCB3aGljaCBhcmUgcmVwcmVzZW50YXRpdmUgb2YgYSB0eXBpY2FsIGRhdGEgc2NpZW5jZSBwcm9qZWN0OiAKCjEpIFNpbXVsYXRpbmcgZGF0YTsKCjIpIFdyaXRpbmcgYW5kIHJ1bm5pbmcgdmFyaW91cyBhbGdvcml0aG1zIGFuZCByZWNvcmRpbmcgZXhlY3V0aW9uIHRpbWUgYW5kIG1lbW9yeSB1c2FnZTsKCjMpIEFuYWx5emluZyBhbmQgcHJlc2VudGluZyB0aGUgZGF0YS4KCgojIyBTaW11bGF0aW9uCgpJIHNpbXVsYXRlZCBhIGxpbmVhciByZWdyZXNzaW9uIGRhdGFzZXQgaW4gUiB3aXRoICRuPTEwXjYkIGFuZCAkcD0xMCQgYXMgZm9sbG93czoKXGJlZ2lue2FsaWduKn0KXGJldGFfaiAmXG92ZXJzZXR7aWlkfXtcc2ltfSBcbWF0aHJte1VuaWZ9KDAsMTApLCBccXVhZCBqPTEsXGRvdHMsIHAgXFwKeF97aWp9ICZcb3ZlcnNldHtpaWR9e1xzaW19IFxtYXRoY2Fse059KDAsMSksIFxxdWFkIGk9MSwgXGRvdHMsbiBcXAp5X2kgJlxvdmVyc2V0e2luZGVwLn17XHNpbX0gXG1hdGhjYWx7Tn0oMTAwK1xzdW1fe2o9MX1ee3B9IFxiZXRhX2ogeF97aWp9LDEpLgpcZW5ke2FsaWduKn0KVGhlICR5JCB2ZWN0b3Igd2FzIHVzZWQgYnkgdGhlIGxvb3AgYW5kIGJvb3RzdHJhcCBhbGdvcml0aG1zLCBhbmQgdGhlIGZ1bGwgJCh5LFgpJCBkYXRhc2V0IHdhcyB1c2VkIGJ5IHRoZSBsaW5lYXIgcmVncmVzc2lvbiBhbmQgTWFya292IGNoYWluIE1vbnRlIENhcmxvIGFsZ29yaXRobXMuCgpJIGFsc28gZ2VuZXJhdGVkIGEgdmVjdG9yICRmJCBhcyBmb2xsb3dzOiAKXGJlZ2lue2FsaWduKn0KSV9pICZcb3ZlcnNldHtpaWR9e1xzaW19IFxtYXRocm17QmVybn0oMS8yKSwgXHF1YWQgaT0xLCBcZG90cywgblxcCmZfaSAmPSAoLTEpXntJX2l9ClxlbmR7YWxpZ24qfQphbmQgdGhlIGRhdHNldCAkKGYsIFgpJCB3YXMgdXNlZCBieSB0aGUgc3VwcG9ydCB2ZWN0b3IgbWFjaGluZSBhbGdvcml0aG0uCgpUd28gJDEwMDBcdGltZXMgMTAwMCQgbWF0cmljZXMgd2VyZSBwb3B1bGF0ZWQgZnJvbSBhIHN0YW5kYXJkIE5vcm1hbCBkaXN0cmlidXRpb24uIFRoZXNlIG1hdHJpY2VzIHdlcmUgdXNlZCBpbiB0aGUgbWF0cml4IG11bHRpcGxpY2F0aW9uIGFuZCBpbnZlcnNpb24gb3BlcmF0aW9ucy4gVGhlIGRhdGEgJCh5LFgsZikkIHdhcyBzYXZlZCBhcyBhIG1hdHJpeCBpbiB0aGUgZmlsZSBEYXRhL2RhdGEuY3N2LCBhbmQgdGhlIHR3byBtYXRyaWNlcyAkQSQgYW5kICRCJCB3ZXJlIHNhdmVkIGluIHRoZSBmaWxlcyBEYXRhL0EuY3N2IGFuZCBEYXRhL0IuY3N2LiBUaGUgc2NyaXB0IHVzZWQgdG8gZ2VuZXJhdGUgdGhlIGRhdGEgaXMgYXZhaWxhYmxlIGluIHRoZSBHaXRodWIgcmVwb3NpdG9yeSAoc2VlIFtyZXByb2R1Y2liaWxpdHldKCNyZXByb2QpKSBhdCBEYXRhL2RhdGEuUiwgYW5kIHRoZSBjc3YgZmlsZXMgZm9yIHRoZSBtYXRyaWNlcyBpcyBhdmFpbGFibGUgaW4gdGhlIHNhbWUgcmVwb3NpdG9yeSB3aGlsZSB0aGUgZGF0YS5jc3YgZmlsZSB3YXMgdG9vIGxhcmdlIHRvIGJlIHVwbG9hZGVkIHRvIEdpdGh1YiBidXQgdGhlIHNhbWUgZGF0YSBJIHVzZWQgY2FuIGJlIGdlbmVyYXRlZCB0aHJvdWdoIHRoZSBmaXhlZCBzZWVkIHNldCBpbiB0aGUgUiBmaWxlLgoKVGhlIFIgY29kZSB1c2VkIHRvIGdlbmVyYXRlIHRoZSBkYXRhIGlzIHZlcnkgc3RyYWlnaHRmb3J3YXJkLgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpzZXQuc2VlZCgzMikKCiMgR2VuZXJhdGUgbGluZWFyIHJlZ3Jlc3Npb24gZGF0YQpiZXRhcyA8LSBydW5pZigxMCwgLTEwLCAxMCkKWCA8LSBtYXRyaXgocm5vcm0oMWU2ICogMTApLCBuY29sID0gMTApCnkgPC0gMTAwICsgWFssMToxMF0gJSolIGJldGFzICsgcm5vcm0oMWU2KQpmIDwtIHNhbXBsZShjKC0xLCAxKSwgMWU2LCByZXBsYWNlID0gVFJVRSkKZGF0YSA8LSBjYmluZCh5LCBYLCBmKQp3cml0ZS5jc3YoZGF0YSwgIkRhdGEvZGF0YS5jc3YiKQoKIyBHZW5lcmF0ZSBtYXRyaXggZGF0YQpBIDwtIG1hdHJpeChydW5pZigxZTYpLCBuY29sID0gMWUzKQpCIDwtIG1hdHJpeChydW5pZigxZTYpLCBuY29sID0gMWUzKQp3cml0ZS5jc3YoQSwgIkRhdGEvQS5jc3YiKQp3cml0ZS5jc3YoQiwgIkRhdGEvQi5jc3YiKQpgYGAKCgoKIyMgQWxnb3JpdGhtcwoKRm9yIGVhY2ggYWxnb3JpdGhtLCBJIG1lYXN1cmUgZXhlY3V0aW9uIHRpbWUgYW5kIG1lbW9yeSB1c2luZyB0aGUgImJlbmNobWFyayIgcGFja2FnZSBpbiBSIGFuZCB0aGUgInRpbWUiIGFuZCAibWVtb3J5X3Byb2ZpbGVyIiBsaWJyYXJpZXMgaW4gUHl0aG9uLiBUaGUgbWVhc3VyZWQgdGltZSB3YXMgdG90YWwgdXNlciB0aW1lIHJhdGhlciB0aGFuIHByb2Nlc3NvciB0aW1lLiBUaGUgYWxnb3JpdGhtcyB3ZXJlIG1lYW50IHRvIGJlIHdyaXR0ZW4gaW4gYSB3YXkgdGhhdCBpcyBhcyBzdHJ1Y3R1cmFsbHkgc2ltaWxhciBhcyBwb3NzaWJsZSBpbiBib3RoIGxhbmd1YWdlcywgYnV0IHRoZSBpbXBsZW1lbnRhdGlvbnMgb2YgYmFzZSBmdW5jdGlvbnMgdmFyeSBncmVhdGx5IGJldHdlZW4gdGhlIHR3byBsYW5ndWFnZXMuIFVsdGltYXRlbHksIHRoaXMgaXMgd2hhdCBtYWtlcyB0aGUgY29tcGFyaXNvbiBpbnRlcmVzdGluZyBzaW5jZSB0aGVyZSBjYW4gYmUgZ3JlYXQgZGlmZmVyZW5jZXMgaW4gZXhlY3V0aW9uIHRpbWUgZm9yIHNpbWlsYXIgb3BlcmF0aW9ucywgYnV0IGl0IGFsc28gbWVhbnMgdGhhdCBhbiBhbGdvcml0aG0gY2FuIGJlIHdyaXR0ZW4gaW4gYSBwb29ybHkgb3B0aW1pemVkIHdheSBpbiBhIGxhbmd1YWdlIGFuZCBhcnRpZmljaWFsbHkgc2VlbSBtdWNoIGZhc3RlciBpbiB0aGUgb3RoZXIuIEZvciBpbnN0YW5jZSwgcGVvcGxlIG9mdGVuIGNyaXRpY2l6ZSB0aGUgc3BlZWQgb2YgbG9vcHMgaW4gUiBhbmQgUHl0aG9uIGFuZCBteSB0ZXN0cyBhZ3JlZSB3aXRoIHRoaXMgY3JpdGljaXNtLCBidXQgdGhlc2Ugb3BlcmF0aW9ucyBjYW4gYWxtb3N0IGFsd2F5cyBiZSBtYWRlIG11Y2ggZmFzdGVyIGJ5IHVzaW5nIHZlY3Rvcml6ZWQgb3BlcmF0aW9ucy4KCk1lbW9yeSB0dXJuZWQgb3V0IGRpZmZpY3VsdCB0byBtZWFzdXJlLCBhbmQgSSByZWxpZWQgb24gZXh0ZXJuYWwgcGFja2FnZXMuIFIgYW5kIFB5dGhvbiBpbXBsZW1lbnQgbWVtb3J5IG1hbmFnZW1lbnQgaW4gdmVyeSBkaWZmZXJlbnQgd2F5cy4gRm9yIGluc3RhbmNlLCBSIGRpc3Rpbmd1aXNoZXMgYmV0d2VlbiBtZW1vcnkgc3RvcmVkIGJ5IHZlY3RvcnMgYW5kIGJ5IGV2ZXJ5dGhpbmcgZWxzZS4gSXQgYXBwZWFycyB0aGF0IG1lbW9yeSB1c2FnZSB3YXMgdG9vIGxvdyB0byBiZSByZWNvcmRlZCBpbiBSIGZvciBzZXZlcmFsIG9wZXJhdGlvbnMgc3VjaCBhcyBsb29wcyBhbmQgd2FzIHJlY29yZGVkIGFzIDAuIFRoaXMgaXMgYSBmbG9hdGluZyBwb2ludCBwcm9ibGVtIHRoYXQgSSB3YXMgbm90IGFibGUgdG8gc29sdmUgZGVzcGl0ZSB0cnlpbmcgbWFueSBhcHByb2FjaGVzLCBpbmNsdWRpbmcgdGhlIGJhc2UgUiBpbXBsZW1lbnRhdGlvbiBvZiBtZW1vcnkgbW9uaXRvcmluZyBhbmQgc2V2ZXJhbCBwYWNrYWdlcy4gVGhlIGludGVycHJldGF0aW9uIG9mIG1lbW9yeSB1c2FnZSBpcyBhbHNvIG11cmt5LCBhcyBkZXRhaWxlZCBpbiB0aGUgW0RpY3Vzc2lvbl0oI2Rpc2N1c3Npb24pIHNlY3Rpb24uCgpUaGUgZm9sbG93aW5nIGFsZ29yaXRobXMgd2VyZSB0ZXN0ZWQ6IGEgc3VtIGxvb3AsIGEgZ2VvbWV0cmljIG1lYW4gbG9vcCBhbmQgYSB2ZWN0b3JpemVkIGltcGxlbWVudGF0aW9uLCBtYXRyaXggbXVsdGlwbGljYXRpb24gYW5kIGludmVyc2lvbiwgbGluZWFyIHJlZ3Jlc3Npb24sIHRoZSBib290c3RyYXAsIHR3byBNYXJrb3YgQ2hhaW4gTW9udGUgQ2FybG8gKE1DTUMpIGFsZ29yaXRobXMsIGFuZCBhIHN1cHBvcnQgdmVjdG9yIG1hY2hpbmUgKFNWTSkuIFdoZW4gcG9zc2libGUsIEkgaW1wbGVtZW50ZWQgYSBzaW1wbGUgdmVyc2lvbiBvZiB0aGVzZSBhbGdvcml0aG1zIHVzaW5nIG9ubHkgYmFzZSBmdW5jdGlvbnMgaW4gdGhlIGxhbmd1YWdlIGFzIHdlbGwgYXMgYSBwb3B1bGFyIHBhY2thZ2UvbGlicmFyeS4gRm9yIGluc3RhbmNlLCBmb3IgbGluZWFyIHJlZ3Jlc3Npb24gSSB1c2UgdGhlIGxtKCkgZnVuY3Rpb24gaW4gUiBhbmQgdGhlIFNjaWtpdC1MZWFybiBsaWJyYXJ5IGluIFB5dGhvbiBhcyB3ZWxsIGFzIGFuIGFsZ29yaXRobSB1c2luZyBvbmx5IG1hdHJpeCBtdWx0aXBsaWNhdGlvbiBhbmQgaW52ZXJzaW9uLiBPZiBjb3Vyc2UsIHRoZXNlIHR3byBtYXRyaXggb3BlcmF0aW9ucyBhcmUgdGhlbXNlbHZlcyBhbGdvcml0aG1zIHdpdGggbWFueSBkaWZmZXJlbnQgcG9zc2libGUgaW1wbGVtZW50YXRpb25zLiBJIG1haW5seSB1c2UgUiBpbiBteSBldmVyeWRheSBsaWZlIHNvIEkgYW0gbGlrZWx5IG1vcmUgZmFtaWxpYXIgd2l0aCB0aGUgbW9zdCBvcHRpbWl6ZWQgcGFja2FnZXMgaW4gUiByYXRoZXIgdGhhbiBQeXRob24uCgpJIHZhcmllZCB0aGUgc2FtcGxlIHNpemUgd2hlbiBydW5uaW5nIHRoZSBhbGdvcml0aG1zIGJ5IHNlbGVjdGluZyBhIHN1YnNldCBvZiB0aGVzZSBkYXRhc2V0cy4gSSB2YXJpZWQgdGhlIHNhbXBsZSBzaXplIGZyb20gJG49MTBeMiQgdG8gJG49MTBeNiQgYnkgbWFnbml0dWRlIG9mIDEwIGZvciB0aGUgbG9vcCBhbmQgbGluZWFyIHJlZ3Jlc3Npb24gYWxnb3JpdGhtcy4gSSB2YXJpZWQgdGhlIHNpemUgb2YgdGhlIG1hdHJpY2VzIGZyb20gJG49MTAkIHRvICRuPTEwXjMkIGJ5IG1hZ25pdHVkZSBvZiAkMTBeezEvMn0kLiBJIGtlcHQgdGhlIHNhbXBsZSBzaXplIG9mIHRoZSBib290c3RyYXAgYW5kIE1DTUMgYWxnb3JpdGhtcyBmaXhlZCBhdCAkbj0xMF4zJCBhbmQgdmFyaWVkIHRoZSBzYW1wbGluZy9yZXNhbXBsaW5nIHNpemUgKHNlZSB0aGUgcHJlY2lzZSBkZWZpbml0aW9uIG9mIHRoZSBhbGdvcml0aG1zIGluIHRoZSBbUmVzdWx0c10oI3Jlc3VsdHMpIHNlY3Rpb24pIGZyb20gJEI9MTAkIHRvICQxMF41JCBieSBtYWduaXR1ZGUgb2YgMTAuIEZvciB0aGUgU1ZNIGFsZ29yaXRobSwgSSB2YXJpZWQgdGhlIHNhbXBsZSBzaXplIGZyb20gJG49MTAkIHRvICRuPTEwXjMkIGJ5IG1hZ25pdHVkZSBvZiAkMTBeezEvMn0kLiBUaGUgc2FtcGxlIHNpemVzIHdlcmUgY2hvc2VuIHRvIHJlcHJlc2VudCB0aGUgc2hpZnQgZnJvbSB0aGUgdHlwaWNhbCBzaXplIG9mIGEgc3RhdGlzdGljYWwgYW5hbHlzaXMgYXJvdW5kICRuPTEwMCQgdG8gdGhlIGJpZyBkYXRhIHNpemUgb2YgJG49MTBeNiQgd2hpY2ggYXBwcm9hY2hlcyB0aGUgbGltaXRzIG9mIG15IG1hY2hpbmUgZm9yIG1hbnkgYWxnb3JpdGhtcy4gVGhlIHNhbXBsZSBzaXplIHdhcyB2YXJpZWQgYXQgbG93ZXIgcmF0ZXMgZm9yIHNvbWUgYWxnb3JpdGhtcyBiZWNhdXNlIGV4ZWN1dGlvbiB0aW1lIGluY3JlYXNlZCB0b28gcmFwaWRseSBmb3IgdGhlIGJvb3RzdHJhcCwgTWFya292IGNoYWluIE1vbnRlIENhcmxvIGFsZ29yaXRobXMsIGFuZCB0aGUgc3VwcG9ydCB2ZWN0b3IgbWFjaGluZS4KCkkgcmFuIHNjcmlwdHMgaW4gUiBhbmQgUHl0aG9uIG9uIHRoZXNlIHNpbXVsYXRlZCBkYXRhc2V0cyBmb3IgMTAgaXRlcmF0aW9ucyBhbmQgZm9yIHRoZSB2YXJ5aW5nIHNhbXBsZSBzaXplcyBtZW50aW9uZWQgYWJvdmUsIGFuZCBJIHJlY29yZGVkIHRoZSBtZWRpYW4gZXhlY3V0aW9uIHRpbWUgYW5kIG1lbW9yeSB1c2UgaW4gQ1NWIGZpbGVzIGluIFJlc3VsdHMvcmVzdWx0c19SLmNzdiBhbmQgUmVzdWx0cy9yZXN1bHRzX3B5dGhvbi5jc3YuIFRoZSBzY3JpcHRzIHVzZWQgdG8gcnVuIHRoZSBhbGdvcml0aG1zIGFyZSBhdmFpbGFibGUgaW4gdGhlIEdpdEh1YiByZXBvc2l0b3J5IGF0IFNjcmlwdHMvYWxnb3JpdGhtcy5weSBhbmQgU2NyaXB0cy9hbGdvcml0aG1zLlIuIAoKIyMgQW5hbHlzaXMKCkkgdXNlZCBSIE1hcmtkb3duIHRvIGdlbmVyYXRlIHRoaXMgSFRNTCB3ZWJwYWdlIGFuZCB1c2VkIHRoZSBwbG90bHkgcGFja2FnZSB0byBnZW5lcmF0ZSBpbnRlcmFjdGl2ZSBwbG90cyBvZiBleGVjdXRpb24gdGltZSBhbmQgbWVtb3J5IHVzZSBmb3IgZWFjaCBhbGdvcml0aG0uIEkgYWxzbyBkaXNjdXNzIHRoZW9yZXRpY2FsIEJpZy1PIGNvbXBsZXhpdHkgYW5kIGZpdCBhbiBlbXBpcmljYWwgY3VydmUgdG8gdGhlIHBsb3RzIGJhc2VkIG9uIHRoZSBtb3N0IHNpbXBsZSB2ZXJzaW9uIG9mIHRoZXNlIGFsZ29yaXRobXMuCgojIyBSZXByb2R1Y2liaWxpdHkgeyNyZXByb2R9CkRldGFpbHMgb24gcmVwcm9kdWNpbmcgdGhlIGVudGlyZSBwcm9qZWN0IGNhbiBiZSBmb3VuZCBhdCBodHRwczovL2dpdGh1Yi5jb20vbGVvdmFuY2l1L2NzMzJfZmluYWxfcHJvamVjdC4gVGhlIGNvZGUgZm9yIHRoaXMgUm1hcmtkb3duIGZpbGUgY2FuIGJlIGRvd25sb2FkZWQgYnkgY2xpY2tpbmcgb24gdGhlIGNvZGUgYnV0dG9uIGF0IHRoZSB0b3Agb2YgdGhpcyB3ZWJwYWdlIG9yIGluIHRoZSBHaXRIdWIgcmVwb3NpdG9yeSBvZiB0aGUgd2VicGFnZSBhdCBodHRwczovL2dpdGh1Yi5jb20vbGVvdmFuY2l1L2xlb3ZhbmNpdS5naXRodWIuaW8uIEFsbCB0aGUgc2NyaXB0cyBmb3Igc2ltdWxhdGluZyB0aGUgZGF0YSBhbmQgcnVubmluZyB0aGUgc2NyaXB0cyBhcmUgYXZhaWxhYmxlIGluIHRoZSBHaXRIdWIgcmVwb3NpdG9yeSBjczMyX2ZpbmFsX3Byb2plY3QsIGFuZCBhbGwgdGhlIGNvZGUgdXNlZCB0byBnZW5lcmF0ZSBteSB3ZWJzaXRlIHdoaWNoIHdhcyBmb3JrZWQgZnJvbSBtbWlzdGFrZXMvbWluaW1hbC1taXN0YWtlcyBpcyBhdmFpbGFibGUgaW4gdGhlIEdpdGh1YiByZXBvc2l0b3J5IGxlb3ZhbmNpdS5naXRodWIuaW8uCgpJIGFtIHVzaW5nIFIgdmVyc2lvbiA0LjMuMSB3aXRoIFJTdHVkaW8gdmVyc2lvbiAyMDIzLjA5LjArNDYzIGFuZCBQeXRob24gdmVyc2lvbiAzLjEyLjMgd2l0aCBWaXN1YWwgU3R1ZGlvIGNvZGUgdmVyc2lvbiAxLjg4LjEuIE15IGNvbXB1dGVyIGlzIGEgTWFjQm9vayBQcm8gKDEzLWluY2gsIE0xLCAyMDIwKSB3aXRoIDggR0Igb2YgbWVtb3J5IGFuZCBydW5uaW5nIG9uIG1hY09TIE1vbnRlcmV5IFZlcnNpb24gMTIuMi4xLgoKIyBSZXN1bHRzIHsjcmVzdWx0c30gCgojIyBEYXRhCkEgaGlzdG9ncmFtIG9mIHRoZSBzaW11bGF0ZWQgZGF0YSBmb3IgJG49MTBeNSQgaXMgcGxvdHRlZCBiZWxvdy4gSXQgaGFzIG1lYW4gMTAwIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24gMTcuIEJ5IGNvbnN0cnVjdGlvbiwgaXQgaXMgTm9ybWFsbHkgZGlzdHJpYnV0ZWQuCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCBldmFsID0gVFJVRX0KbGlicmFyeShwbG90bHkpCmRhdGEgPC0gcmVhZC5jc3YoIkRhdGEvZGF0YS5jc3YiKQpoaXN0IDwtIHBsb3RfbHkoeCA9IGRhdGFbMToxZTUsMl0sIHR5cGUgPSAiaGlzdG9ncmFtIikgJT4lCiAgbGF5b3V0KHRpdGxlID0gIlNpbXVsYXRlZCBEYXRhIiwKICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIkZyZXF1ZW5jeSIpLAogICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gInkiKSkKaGlzdApgYGAKCgoKIyMgTG9vcCBvcGVyYXRpb25zCgpGb3IgbG9vcHMgYXJlIHViaXF1aXRvdXMgcHJvZ3JhbW1pbmcgc3RydWN0dXJlcyBpbiBkYXRhIHNjaWVuY2UsIGFuZCB0aGV5IGFyZSBzb21ld2hhdCBpbmZhbW91cyBpbiBib3RoIFIgYW5kIFB5dGhvbiwgYXMgdGhleSBhcmUgb2Z0ZW4gc2FpZCB0byBiZSBtdWNoIHNsb3dlciB0aGFuIGluIGxvdy1sZXZlbCBsYW5ndWFnZXMgbGlrZSBDLgoKIyMjIExvb3Agc3VtcwoKSGVyZSBpcyBwc2V1ZG8gY29kZSBvZiBhIHNpbXBsZSBmb3JlYWNoIGxvb3AgdG8gY29tcHV0ZSBhIHN1bS4KCmBgYHBsYWludGV4dApGdW5jdGlvbiBsb29wX3N1bShuKQogICAgc3VtIDwtIDAKICAgIEZvciBpIGZyb20gMSB0byBuCiAgICAgICAgc3VtIDwtIHN1bSArIGkKICAgIEVuZCBGb3IKICAgIFJldHVybiBzdW0KRW5kIEZ1bmN0aW9uCmBgYAoKSW4gdGVybXMgb2YgY29tcHV0YXRpb25hbCBjb21wbGV4aXR5LCB0aGUgaW5pdGlhbGl6YXRpb24gc3RlcCBpcyAkTygxKSQsIGEgbGluZWFyIG9wZXJhdGlvbiBsaWtlIGEgc3VtIGlzIGFsc28gJE8oMSkkLCBhbmQgYnkgZGVmaW5pdGlvbiB0aGUgbG9vcCBpcyAkTyhuKSQgd2hpY2ggaXMgdGhlIG92ZXJhbGwgY29tcGxleGl0eSBvZiB0aGUgYWxnb3JpdGhtLiAKClRoZSBmb2xsb3dpbmcgY29kZSBzaG93cyB0aGUgaW1wbGVtZW50YXRpb24gaW4gUHl0aG9uLgpgYGB7cHl0aG9uLCBldmFsID0gRkFMU0V9CmRlZiBsb29wX3N1bShuKToKICAgIHN1bSA9IDAKICAgIGZvciBpIGluIHJhbmdlKG4pOgogICAgICAgIHN1bSArPSAxCiAgICByZXR1cm4gc3VtCmBgYAoKVGhlIGZvbGxvd2luZyBjb2RlIHNob3dzIHRoZSBpbXBsZW1lbnRhdGlvbiBpbiBSLgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KbG9vcF9zdW0gPC0gZnVuY3Rpb24obikgewogIHN1bSA8LSAwCiAgZm9yIChpIGluIDE6bikgewogICAgc3VtIDwtIHN1bSArIDEKICB9CiAgcmV0dXJuKHN1bSkKfQpgYGAKCgpOb3RlIHRoYXQgdGhlIHN5bnRheCBmb3Igd3JpdGluZyBmdW5jdGlvbnMgaXMgc2xpZ2h0bHkgZGlmZmVyZW50IGluIFIgYW5kIFB5dGhvbi4gQm90aCBsYW5ndWFnZXMgdXNlIGZvcmVhY2ggbG9vcHMgd3JpdHRlbiBzaW1pbGFybHkuIFIgc3VwcG9ydHMgYm90aCA8LSBhbmQgPSBhcyBhc3NpZ25lbWVudCBvcGVyYXRvcnMuIEhpc3RvcmljYWxseSwgY29tZXMgZnJvbSB0aGUgUyBsYW5ndWFnZSB3aGljaCBhbHNvIHVzZXMgdGhlIDwtIGFzc2lnbm1lbnQgb3BlcmF0b3IuCgoKSSBpbmNsdWRlIGJlbG93IHRoZSBjb2RlIHVzZWQgdG8gZ2VuZXJhdGUgaW50ZXJhY3RpdmUgcGxvdHMgb2YgZXhlY3V0aW9uIHRpbWUgYW5kIG1lbW9yeSB1c2FnZSB3aXRoIHRoZSBwbG90bHkgbGlicmFyeSBpbiBSLiBHZW5lcmF0aW5nIHRoaXMgcGxvdCByZXF1aXJlZCBxdWl0ZSBhIGJpdCBvZiBkYXRhIG1hbmlwdWxhdGlvbiwgd2hpY2ggd2FzIGVhc2lseSBkb25lIGluIGJhc2UgUiwgYW5kIG1vcmUgY29tcGxleCBvcGVyYXRpb25zIGNhbiBiZSBkb25lIHdpdGggc2ltcGxlIHN5bnRheCB3aXRoIHRoZSBkcGx5ciBwYWNrYWdlLCB3aGljaCBJIHVzZWQgd2hlbiBzdG9yaW5nIHRoZSByZXN1bHRzIGZyb20gdGhlIGJlbmNobWFya3MuIEFsbCBvdGhlciBwbG90cyB3ZXJlIGdlbmVyYXRlZCB3aXRoIHNpbWlsYXIgY29kZSB3aGljaCBJIGRvIG5vdCBzaG93IGZvciByZWFkYWJpbGl0eS4KCgpgYGB7cixtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KHBsb3RseSkKIyBSZWFkIGRhdGEKUiA8LSByZWFkLmNzdigiUmVzdWx0cy9SZXN1bHRzX1IuY3N2IikKcHl0aG9uIDwtIHJlYWQuY3N2KCJSZXN1bHRzL1Jlc3VsdHNfcHl0aG9uLmNzdiIpCm1lcmdlZF9kYXRhIDwtIG1lcmdlKFIsIHB5dGhvbiwgYnk9YygiQWxnb3JpdGhtIiwgIm4iKSwgc3VmZml4ZXMgPSBjKCJSIiwgInB5dGhvbiIpKQphbGdvcml0aG1fbmFtZXMgPC0gYygnbG9vcF9zdW0nLCdsb29wX2dlb21fbWVhbicsICd2ZWN0b3JpemVkX2dlb21fbWVhbicsICdtYXRyaXhfbXVsdGlwbGljYXRpb24nLCAKICAgICAgICAgICAgICAgICAnbWF0cml4X2ludmVyc2lvbicsICdsaW5lYXJfcmVncmVzc2lvbl9wYWNrYWdlJywgJ2xpbmVhcl9yZWdyZXNzaW9uX2Jhc2UnLCAKICAgICAgICAgICAgICAgICAnYm9vdHN0cmFwX3BhY2thZ2UnLCAnYm9vdHN0cmFwX2Jhc2UnLCAnc3ZtX3BhY2thZ2UnLCAnc3ZtX2Jhc2UnLCAKICAgICAgICAgICAgICAgICAnTWV0cm9wb2xpc19IYXN0aW5ncycsICdNQ01DX3N0YW4nKQoKIyBDb21wdXRlIEJpZy1PIGNvbXBsZXhpdHkKYWxnb3MgPC0gYygibG9vcF9zdW0iKSAjIFdlIG9ubHkgcGxvdCBvbmUgYWxnb3JpdGhtIGhlcmUgYnV0IGluIG90aGVyIHBsb3RzIEkgaGF2ZSBtdWx0aXBsZSBhbGdvcml0aG1zCm5fdmFsdWVzIDwtIHNlcSgyLCA2LCBieSA9IDAuMSkKT19uIDwtIDEwXm5fdmFsdWVzLzEwXjQKT19uX3B5dGhvbl8xIDwtIE9fbipweXRob25bcHl0aG9uJEFsZ29yaXRobSA9PSBhbGdvc1sxXSxdJFRpbWVbM10gIyBUaGUgcHJlLWZhY3RvcnMgYXJlIHN0YW5kYXJkaXplZCBvbiB0aGUgZW1waXJpY2FsIHRpbWUgd2l0aCBuPTEwXjQKT19uX1JfMSA8LSBPX24qUltSJEFsZ29yaXRobSA9PSBhbGdvc1sxXSxdJFRpbWVbM10KCiMgVGltZSBwbG90CnBsb3RfbHkoKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IFJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSwgeCA9IH5sb2cobiwgMTApLCB5ID0gflRpbWUsIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ1InLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJibHVlIiwgZGFzaCA9ICdzb2xpZCcpLAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBzeW1ib2wgPSAnY2lyY2xlJykpICU+JQogIGFkZF90cmFjZShkYXRhID0gcHl0aG9uW3B5dGhvbiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSwgeCA9IH5sb2cobiwgMTApLCB5ID0gflRpbWUsIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ2xvb3Bfc3VtIFB5dGhvbicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gInJlZCIsIGRhc2ggPSAnc29saWQnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJyZWQiLCBzeW1ib2wgPSAnZG90JykpICU+JQogIGFkZF90cmFjZSh4ID0gbl92YWx1ZXMsIHkgPSBPX25fcHl0aG9uXzEsIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMnLAogICAgICAgICAgICBuYW1lID0gJ08obiknLCBsaW5lID0gbGlzdChjb2xvciA9ICdibGFjaycsIGRhc2ggPSAnZGFzaCcpKSAlPiUKICBhZGRfdHJhY2UoeCA9IG5fdmFsdWVzLCB5ID0gT19uX1JfMSwgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcycsCiAgICAgICAgICAgIG5hbWUgPSAnTyhuKScsIGxpbmUgPSBsaXN0KGNvbG9yID0gJ2JsYWNrJywgZGFzaCA9ICdkYXNoJyksIHNob3dsZWdlbmQgPSBGQUxTRSkgJT4lCiAgbGF5b3V0KHRpdGxlID0gIkV4ZWN1dGlvbiBUaW1lIiwKICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gImxvZyhuKSIsIHRpY2ttb2RlID0gImFycmF5IiwgdGlja3ZhbHMgPSAwOjYsIHRpY2t0ZXh0ID0gYXMuY2hhcmFjdGVyKDA6NikpLAogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiVGltZSAocykiKSwKICAgICAgICAgbGVnZW5kID0gbGlzdCh0aXRsZSA9ICJMZWdlbmQiLCBvcmllbnRhdGlvbiA9ICJoIiwgeCA9IDAuMywgeSA9IC0wLjIpKQpgYGAKCgpgYGB7ciBlY2hvPUZBTFNFfQojIE1lbW9yeSBwbG90CnBsb3RfbHkoKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IFJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSwgeCA9IH5sb2cobiwgMTApLCB5ID0gfk1lbW9yeSwgCiAgICAgICAgICAgIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMrbWFya2VycycsIG5hbWUgPSAnbG9vcF9zdW0gUicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBkYXNoID0gJ3NvbGlkJyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIHN5bWJvbCA9ICdjaXJjbGUnKSkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBweXRob25bcHl0aG9uJEFsZ29yaXRobSA9PSBhbGdvc1sxXSxdLCB4ID0gfmxvZyhuLCAxMCksIHkgPSB+TWVtb3J5LCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdsb29wX3N1bSBQeXRob24nLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJyZWQiLCBkYXNoID0gJ3NvbGlkJyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAicmVkIiwgc3ltYm9sID0gJ2RvdCcpKSAlPiUKICBsYXlvdXQodGl0bGUgPSAiTWVtb3J5IFVzYWdlIiwKICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gImxvZyhuKSIsIHRpY2ttb2RlID0gImFycmF5IiwgdGlja3ZhbHMgPSAwOjYsIHRpY2t0ZXh0ID0gYXMuY2hhcmFjdGVyKDA6NikpLAogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiTWVtb3J5IChNQikiKSwKICAgICAgICAgbGVnZW5kID0gbGlzdCh0aXRsZSA9ICJMZWdlbmQiLCBvcmllbnRhdGlvbiA9ICJoIiwgeCA9IDAuMywgeSA9IC0wLjIpKQpgYGAKCgpXZSBzZWUgdGhhdCBsb29wcyBpbiBSIHJ1biB0d28gdG8gdGhyZWUgdGltZXMgZmFzdGVyIHRoYW4gaW4gUHl0b24uIFdlIHNlZSB0aGF0ICRPKG4pJCBmaXRzIHRoZSBkYXRhIHBlcmZlY3RseS4gSXQgc2VlbXMgdGhhdCBtZW1vcnkgdXNhZ2Ugd2FzIHRvbyBsb3cgdG8gYmUgcmVjb3JkZWQgaW4gUiBhbmQgdGhlcmUgaXMgbm8gY2xlYXIgdHJlbmQgb24gdGhlIG1lbW9yeSB1c2FnZSBpbiBQeXRob24sIGFsdGhvdWdoIGl0IFB5dGhvbiBkb2VzIHVzZSBhIHN0cmFuZ2VseSBsYXJnZSBhbW91bnQgb2YgbWVtb3J5IGZvciBzdWNoIGEgc2ltcGxlIGZvciBsb29wIGFsZ29yaXRobS4gVGhpcyBpcyBhIHdpbiBmb3IgZXhlY3V0aW9uIHRpbWUgZm9yIFIsIGJ1dCBpdCBpcyB3ZWxsLWtub3duIHRoYXQgbG9vcHMgYXJlIHNsb3cgaW4gYm90aCBSIGFuZCBQeXRob24gZHVlIHRvIHRoZSBtYW55IGFic3RyYWN0aW9ucyBtYWRlIGJ5IHRoZSBwcm9ncmFtbWluZyBsYW5ndWFnZXMuIEl0IGlzIG9mdGVuIHN1Z2dlc3RlZCB0byB2ZWN0b3JpemUgb3BlcmF0aW9ucywgd2hpY2ggY2FuIHNwZWVkIHVwIGxvb3BzIGNvbnNpZGVyYWJseS4gCgojIyMgVmVjdG9yaXplZCBvcGVyYXRpb25zCgoKVmVjdG9yaXplZCBvcGVyYXRpb25zIGFyZSBvcGVyYXRpb25zIGFwcGxpZWQgdG8gZXZlcnkgZW50cnkgb2YgYW4gYXJyYXkuIFRoZXkgYXJlIHN0aWxsIGxvb3BzLCBidXQgdGhleSBhcmUgdHlwaWNhbGx5IGNvbXBpbGVkIGRpcmVjdGx5IGluIEMgd2hpY2ggaXMgbXVjaCBmYXN0ZXIgdGhhbiBleGVjdXRpbmcgYSBsb29wIGluIFIgb3IgUHl0aG9uLiBUaGUgY29tcHV0YXRpb25hbCBjb21wbGV4aXR5IHRoZW9yZXRpY2FsbHkgc2hvdWxkIHN0aWxsIGJlIHRoZSBzYW1lIGFzIGEgbG9vcCBzaW5jZSB0aGUgc2FtZSBvcGVyYXRpb25zIGFyZSBwZXJmb3JtZWQsIGJ1dCB0aGUgcHJlLWZhY3RvciBpcyByZWR1Y2VkLiBJbiBhZGRpdGlvbiwgdGhlIG9wZXJhdGlvbnMgY2FuIGJlIG9wdGltaXplZCB0byBwZXJmb3JtIHBhcmFsbGVsIG9wZXJhdGlvbnMgb24gdGhlIGRhdGEsIHNvIGl0IGlzIGFsc28gcG9zc2libGUgZm9yIHRoZSBvcmRlciBvZiBleGVjdXRpb24gdGltZSB0byBiZSByZWR1Y2VkIGFzIHdlbGwuIEluIHRoZSBmb2xsb3dpbmcgcGxvdHMsIEkgY29tcGFyZSB0aGUgY29tcHV0YXRpb24gb2YgdGhlIGdlb21ldHJpYyBtZWFuICQkXGxlZnQoXHByb2Rfe2k9MX1ebiB5X2kgXHJpZ2h0KV57MS9ufSQkIHdpdGggYSBsb29wIGFsZ29yaXRobSBhbmQgd2l0aCBhIHZlY3Rvcml6ZWQgb3BlcmF0aW9uLiBJdCBpcyBjb252ZW5pZW50IHRvIHdvcmsgb24gYSBsb2cgc2NhbGUgdG8gYXZvaWQgb3ZlcmZsb3cgaXNzdWVzLCBzbyBJIGNvbXB1dGUgdGhlIGVxdWl2YWxlbnQgZm9ybSAKJCRcZXhwXGxlZnQoXGZyYWN7MX17bn0gXHN1bV97aT0xfV5uIFxsb2cgeV9pXHJpZ2h0KS4kJAoKCgpSIGhhcyBhIGRpc3RpbmN0IHZlY3RvciBvYmplY3QgdHlwZSwgd2hpbGUgaW4gUHl0aG9uIHRoZXkgYXJlIHNpbXBseSBvbmUtZGltZW5zaW9uYWwgYXJyYXlzLiBWZWN0b3JpemVkIG9wZXJhdGlvbnMgYXJlIGltcGxlbWVudGVkIGluIFB5dGhvbiB0aHJvdWdoIGxpYnJhcmllcyBzdWNoIGFzIG51bXB5IGFuZCB0aGV5IGFyZSBpbXBsZW1lbnRlZCBuYXRpdmVseSBpbiBSIGVpdGhlciB0aHJvdWdoIGltcGxpY2l0IG5vdGF0aW9uIGFzIGluIHB5dGhvbiBvciB3aXRoIGEgY2FsbCB0byB0aGUgZnVuY3Rpb24gYXBwbHkuIFRoZXJlIGFyZSBhbHNvIHNldmVyYWwgcGFja2FnZXMgaW4gUiBpbXBsZW1lbnRpbmcgZWZmaWNpZW50IHZlY3Rvcml6ZWQgb3BlcmF0aW9ucy4gSGVyZSBpcyB0aGUgY29kZSBpbiBQeXRob246CmBgYHtweXRob24sIGV2YWwgPSBGQUxTRX0KZGVmIHZlY3Rvcml6ZWRfZ2VvbV9tZWFuKGRhdGEpOgogICAgcmV0dXJuIG5wLmV4cChucC5zdW0obnAubG9nKGRhdGEpKSAvIGxlbihkYXRhKSkKYGBgCgphbmQgaW4gUjoKCmBgYHtyLCBldmFsID0gRkFMU0V9CnZlY3Rvcml6ZWRfZ2VvbV9tZWFuIDwtIGZ1bmN0aW9uKGRhdGEpIHsKICByZXR1cm4oZXhwKG1lYW4obG9nKGRhdGEpKSkpCn0KYGBgCgoKSGVyZSBhcmUgcGxvdHMgc2hvd2luZyBleGVjdXRpb24gdGltZSBhbmQgbWVtb3J5IGZvciBwbG90cyBhbmQgdmVjdG9yaXplZCBvcGVyYXRpb25zIGluIFIgYW5kIFB5dGhvbi4KCgpgYGB7ciBlY2hvPUZBTFNFfQphbGdvcyA8LSBjKCJsb29wX2dlb21fbWVhbiIsICJ2ZWN0b3JpemVkX2dlb21fbWVhbiIpCnBsb3RfbHkoKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IFJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSwgeCA9IH5sb2cobiwgMTApLCB5ID0gflRpbWUsIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ2xvb3BfZ2VvbV9tZWFuIFInLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJibHVlIiwgZGFzaCA9ICdzb2xpZCcpLAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBzeW1ib2wgPSAnY2lyY2xlJykpICU+JQogIGFkZF90cmFjZShkYXRhID0gUltSJEFsZ29yaXRobSA9PSBhbGdvc1syXSxdLCB4ID0gfmxvZyhuLCAxMCksIHkgPSB+VGltZSwgCiAgICAgICAgICAgIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMrbWFya2VycycsIG5hbWUgPSAndmVjdG9yaXplZF9nZW9tX21lYW4gUicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBkYXNoID0gJ2Rhc2hkb3QnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJibHVlIiwgc3ltYm9sID0gJ3NxdWFyZScpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IHB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzFdLF0sIHggPSB+bG9nKG4sIDEwKSwgeSA9IH5UaW1lLCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdsb29wX2dlb21fbWVhbiBQeXRob24nLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJyZWQiLCBkYXNoID0gJ3NvbGlkJyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAicmVkIiwgc3ltYm9sID0gJ2RvdCcpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IHB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzJdLF0sIHggPSB+bG9nKG4sIDEwKSwgeSA9IH5UaW1lLCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICd2ZWN0b3JpemVkX2dlb21fbWVhbiBQeXRob24nLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJyZWQiLCBkYXNoID0gJ2Rhc2hkb3QnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJyZWQiLCBzeW1ib2wgPSAnc3F1YXJlJykpICU+JQogIGxheW91dCh0aXRsZSA9ICJFeGVjdXRpb24gVGltZSIsCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJsb2cobikiLCB0aWNrbW9kZSA9ICJhcnJheSIsIHRpY2t2YWxzID0gMDo2LCB0aWNrdGV4dCA9IGFzLmNoYXJhY3RlcigwOjYpKSwKICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIlRpbWUgKHMpIiksCiAgICAgICAgIGxlZ2VuZCA9IGxpc3QodGl0bGUgPSAiTGVnZW5kIiwgb3JpZW50YXRpb24gPSAiaCIsIHggPSAwLjMsIHkgPSAtMC4yKSkKCnBsb3RfbHkoKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IFJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSwgeCA9IH5sb2cobiwgMTApLCB5ID0gfk1lbW9yeSwgCiAgICAgICAgICAgIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMrbWFya2VycycsIG5hbWUgPSAnbG9vcF9nZW9tX21lYW4gUicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBkYXNoID0gJ3NvbGlkJyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIHN5bWJvbCA9ICdjaXJjbGUnKSkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBSW1IkQWxnb3JpdGhtID09IGFsZ29zWzJdLF0sIHggPSB+bG9nKG4sIDEwKSwgeSA9IH5NZW1vcnksIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ3ZlY3Rvcml6ZWRfZ2VvbV9tZWFuIFInLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJibHVlIiwgZGFzaCA9ICdkYXNoZG90JyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIHN5bWJvbCA9ICdzcXVhcmUnKSkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBweXRob25bcHl0aG9uJEFsZ29yaXRobSA9PSBhbGdvc1sxXSxdLCB4ID0gfmxvZyhuLCAxMCksIHkgPSB+TWVtb3J5LCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdsb29wX2dlb21fbWVhbiBQeXRob24nLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJyZWQiLCBkYXNoID0gJ3NvbGlkJyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAicmVkIiwgc3ltYm9sID0gJ2RvdCcpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IHB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzJdLF0sIHggPSB+bG9nKG4sIDEwKSwgeSA9IH5NZW1vcnksIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ3ZlY3Rvcml6ZWRfZ2VvbV9tZWFuIFB5dGhvbicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gInJlZCIsIGRhc2ggPSAnZGFzaGRvdCcpLAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gInJlZCIsIHN5bWJvbCA9ICdzcXVhcmUnKSkgJT4lCiAgbGF5b3V0KHRpdGxlID0gIk1lbW9yeSB1c2FnZSIsCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJsb2cobikiLCB0aWNrbW9kZSA9ICJhcnJheSIsIHRpY2t2YWxzID0gMDo2LCB0aWNrdGV4dCA9IGFzLmNoYXJhY3RlcigwOjYpKSwKICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIk1lbW9yeSAoTUIpIiksCiAgICAgICAgIGxlZ2VuZCA9IGxpc3QodGl0bGUgPSAiTGVnZW5kIiwgb3JpZW50YXRpb24gPSAiaCIsIHggPSAwLjMsIHkgPSAtMC4yKSkKYGBgCgoKV2Ugc2VlIHRoYXQgbG9vcHMgaW4gUHl0aG9uIGFuZCBSIGFyZSB2ZXJ5IHNsb3cgY29tcGFyZWQgdG8gdGhlIHZlY3Rvcml6ZWQgb3BlcmF0aW9uLiBBbHRob3VnaCB0aGUgcHJlLWZhY3RvciBpcyBtdWNoIHNtYWxsZXIgZm9yIHZlY3Rvcml6ZWQgb3BlcmF0aW9ucywgdGhlIGNvbXB1dGF0aW9uYWwgY29tcGxleGl0eSBpcyBzdGlsbCAkTyhuKSQsIGFzIGNhbiBiZSBzZWVuIGJ5IGZpdHRpbmcgYSBsaW5lYXIgY3VydmUgdG8gdGhlIHBsb3Qgb2YgdmVjdG9yaXplZCBvcGVyYXRpb25zIGJlbG93LgoKCgpgYGB7ciwgZWNobz1GQUxTRX0KYWxnb3MgPC0gYygidmVjdG9yaXplZF9nZW9tX21lYW4iKQpuX3ZhbHVlcyA8LSBzZXEoMiwgNiwgYnkgPSAwLjEpCk9fbiA8LSAxMF5uX3ZhbHVlcy8xMF40Ck9fbl9weXRob25fMSA8LSBPX24qcHl0aG9uW3B5dGhvbiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSRUaW1lWzNdCk9fbl9SXzEgPC0gT19uKlJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSRUaW1lWzNdCnBsb3RfbHkoKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IFJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSwgeCA9IH5sb2cobiwgMTApLCB5ID0gflRpbWUsIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ3ZlY3Rvcml6ZWRfZ2VvbV9tZWFuIFB5dGhvbicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBkYXNoID0gJ2Rhc2hkb3QnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJibHVlIiwgc3ltYm9sID0gJ3NxdWFyZScpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IHB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzFdLF0sIHggPSB+bG9nKG4sIDEwKSwgeSA9IH5UaW1lLCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICd2ZWN0b3JpemVkX2dlb21fbWVhbiBQeXRob24nLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJyZWQiLCBkYXNoID0gJ2Rhc2hkb3QnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJyZWQiLCBzeW1ib2wgPSAnc3F1YXJlJykpICU+JQogIGFkZF90cmFjZSh4ID0gbl92YWx1ZXMsIHkgPSBPX25fUl8xLCB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzJywKICAgICAgICAgICAgbmFtZSA9ICdPKG4pJywgbGluZSA9IGxpc3QoY29sb3IgPSAnYmxhY2snLCBkYXNoID0gJ2Rhc2gnKSkgJT4lCiAgbGF5b3V0KHRpdGxlID0gIkV4ZWN1dGlvbiBUaW1lIiwKICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gImxvZyhuKSIsIHRpY2ttb2RlID0gImFycmF5IiwgdGlja3ZhbHMgPSAwOjYsIHRpY2t0ZXh0ID0gYXMuY2hhcmFjdGVyKDA6NikpLAogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiVGltZSAocykiKSwKICAgICAgICAgbGVnZW5kID0gbGlzdCh0aXRsZSA9ICJMZWdlbmQiLCBvcmllbnRhdGlvbiA9ICJoIiwgeCA9IDAuMywgeSA9IC0wLjIpKQpgYGAKCgoKCiMjIE1hdHJpeCBvcGVyYXRpb25zCgoKTWF0cml4IG9wZXJhdGlvbnMsIG5hbWVseSBhZGRpdGlvbiwgc3VidHJhY3Rpb24sIG11bHRpcGxpY2F0aW9uLCBpbnZlcnNpb24sIGFuZCBkb3QgcHJvZHVjdHMgYXJlIGNlbnRyYWwgdG8gc3RhdGlzdGljcyBwYXJ0aWN1bGFybHkgZm9yIG11bHRpdmFyaWF0ZSBkYXRhLiBGb3IgaW5zdGFuY2UsIGxpbmVhciByZWdyZXNzaW9uIGNhbiBiZSBmcmFtZWQgZW50aXJlbHkgYXMgYW4gYWxnb3JpdGhtIGludm9sdmluZyBvbmx5IG1hdHJpeCBtdWx0aXBsaWNhdGlvbiBhbmQgaW52ZXJzaW9uLiBIZXJlLCBJIHRlc3QgdGhlIGltcGxlbWVudGF0aW9uIG9mIG1hdHJpeCBtdWx0aXBsaWNhdGlvbiBhbmQgaW52ZXJzaW9uIGluIFB5dGhvbiBhbmQgUi4gVGhpcyBpcyBhIG9uZS1saW5lciBpbiBib3RoIGxhbmd1YWdlcywgYnV0IHRoZSBhY3R1YWwgYWxnb3JpdGhtcyBmb3IgbWF0cml4IG11bHRpcGxpY2F0aW9uIGFuZCBpbnZlcnNpb24gYXJlIGFjdHVhbGx5IHF1aXRlIGNvbXB1dGF0aW9uYWxseSBjb3N0bHkuCgpGb3IgbWF0cml4IG11bHRpcGxpY2F0aW9uLCB0aGUgbWF0aGVtYXRpY2FsIGRlZmluaXRpb24gZ2l2ZXMgdGhlIGZvbGxvd2luZyBhbGdvcml0aG0uCgpgYGBwbGFpbnRleHQKRnVuY3Rpb24gbXV0aXBseV9tYXRyaWNlcyhBLCBCKQogICAgLy8gRGltZW5zaW9ucwogICAgbSA8LSBudW1iZXIgb2Ygcm93cyBpbiBBCiAgICBuIDwtIG51bWJlciBvZiBjb2x1bW5zIGluIEEKICAgIHAgPC0gbnVtYmVyIG9mIGNvbHVtbnMgaW4gQgogICAgQyA8LSBJbml0aWFsaXplIHdpdGggemVyb3MKICAgIAogICAgRm9yIGkgZnJvbSAxIHRvIG0KICAgICAgICBGb3IgaiBmcm9tIDEgdG8gcAogICAgICAgICAgICBzdW0gPC0gMAogICAgICAgICAgICBGb3IgayBmcm9tIDEgdG8gbgogICAgICAgICAgICAgICAgc3VtIDwtIHN1bSArIEFbaV1ba10gKiBCW2tdW2pdCiAgICAgICAgICAgIEVuZEZvcgogICAgICAgICAgICBDW2ldW2pdIDwtIHN1bQogICAgICAgIEVuZEZvcgogICAgRW5kRm9yCiAgICAKICAgIFJldHVybiBDCkVuZCBGdW5jdGlvbgpgYGAKCkZvciBlYWNoIHBhaXIgb2YgJG0kIHJvdyBmcm9tICRBJCBhbmQgJHAkIGNvbHVtbiBmcm9tICRCJCwgJG4kIG11bHRpcGxpY2F0aW9ucyBhcmUgcGVyZm9ybWVkIHRocm91Z2ggdGhyZWUgbmVzdGVkIGxvb3BzLiBGb3IgZWFjaCBvZiB0aGUgJG4kIG11bHRpcGxpY2F0aW9ucywgdGhlcmUgaXMgYSBzdW0gd2hpY2ggaGFzIGNvbXBsZXhpdHkgJE8oMSkkLiBUaGlzIGdpdmVzIGNvbXB1dGF0aW9uYWwgY29tcGxleGl0eSAkXG1hdGhjYWx7T30obW5wKSQgYW5kIGluIHRoZSBjYXNlIHdoZXJlICRtPW49cCQgdGhpcyBpcyAkTyhuXjMpJC4gSG93ZXZlciwgUHl0aG9uIGFuZCBSIGxpa2VseSBoYXZlIGEgbW9yZSBlZmZpY2llbnQgdmVyc2lvbiB0aHJvdWdoIHNvbWUgY2xldmVyIGFsZ29yaXRobS4gCgoKSGVyZSBpcyBiYXNpYyBwc2V1ZG9jb2RlIGZvciB0aGUgR2F1c3MtSm9yZGFuIGFsZ29yaXRobSBmb3IgbWF0cml4IGludmVyc2lvbi4KYGBgcGxhaW50ZXh0CkZ1bmN0aW9uIGludmVydF9tYXRyaXgoQSkKICAgIC8vIERpbWVuc2lvbnMKICAgIG4gPC0gbnVtYmVyIG9mIHJvd3MgaW4gQQogICAgQiA8LSBJbml0aWFsaXplIG1hdHJpeCBCIGFzIGFuIGlkZW50aXR5IG1hdHJpeAogICAgCiAgICAvLyBQZXJmb3JtIEdhdXNzaWFuIGVsaW1pbmF0aW9uCiAgICBGb3IgaSBmcm9tIDEgdG8gbgogICAgICAgIGRpdmlzb3IgPC0gQVtpXVtpXQogICAgICAgIEZvciBqIGZyb20gMSB0byBuCiAgICAgICAgICAgIEFbaV1bal0gPC0gQVtpXVtqXSAvIGRpdmlzb3IKICAgICAgICAgICAgQltpXVtqXSA8LSBCW2ldW2pdIC8gZGl2aXNvcgogICAgICAgIEVuZEZvcgogICAgICAgIAogICAgICAgIC8vIFNldCBvdGhlciBlbGVtZW50cyBpbiBjb2x1bW4gaSBvZiBBIHRvIDAKICAgICAgICBGb3IgayBmcm9tIDEgdG8gbgogICAgICAgICAgICBJZiBrICE9IGkKICAgICAgICAgICAgICAgIGZhY3RvciA8LSBBW2tdW2ldCiAgICAgICAgICAgICAgICBGb3IgaiBmcm9tIDEgdG8gbgogICAgICAgICAgICAgICAgICAgIEFba11bal0gPC0gQVtrXVtqXSAtIGZhY3RvciAqIEFbaV1bal0KICAgICAgICAgICAgICAgICAgICBCW2tdW2pdIDwtIEJba11bal0gLSBmYWN0b3IgKiBCW2ldW2pdCiAgICAgICAgICAgICAgICBFbmRGb3IKICAgICAgICAgICAgRW5kSWYKICAgICAgICBFbmRGb3IKICAgIEVuZEZvcgogICAgCiAgICBSZXR1cm4gQgpFbmQgRnVuY3Rpb24KYGBgCgpMaWtld2lzZSB0byBtYXRyaXggbXVsdGlwbGljYXRpb24sIHRoZXJlIGFyZSB0aHJlZSBuZXN0ZWQgbG9vcHMgd2hpY2ggZ2l2ZXMgY29tcGxleGl0eSAkXG1hdGhjYWx7T30obl4zKSQuIAoKSW4gUHl0aG9uIHRoaXMgaXMgc2ltcGx5IGltcGxlbWVudGVkIGFzIGZvbGxvd3MuCmBgYHtweXRob24sIGV2YWwgPSBGQUxTRX0KZGVmIG11bHRpcGx5X21hdHJpY2VzKEEsIEIpOgogICAgcmV0dXJuIEEgQCBCCgpkZWYgaW52ZXJ0X21hdHJpeChBKToKICAgIHJldHVybiBucC5saW5hbGcuaW52KEEpCmBgYAoKTGlrZXdpc2UgaW4gUiB0aGlzIGlzIHNpbXBseSBpbXBsZW1lbnRlZCBhcyBmb2xsb3dzLiBTaWRlbm90ZTogSSByZWFsbHkgZG8gbm90IGxpa2UgdGhlIHN5bnRheCAlKiUgZm9yIG1hdHJpeCBtdWx0aXBsaWNhdGlvbiBpbiBSLgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KbXVsdGlwbHlfbWF0cmljZXMgPC0gZnVuY3Rpb24oQSwgQikgewogIHJldHVybihBICUqJSBCKQp9CgppbnZlcnRfbWF0cml4IDwtIGZ1bmN0aW9uKEEpIHsKICByZXR1cm4oc29sdmUoQSkpCn0KYGBgCgoKYGBge3IgZWNobz1GQUxTRX0KYWxnb3MgPC0gYygibWF0cml4X211bHRpcGxpY2F0aW9uIiwgIm1hdHJpeF9pbnZlcnNpb24iKQpuX3ZhbHVlcyA8LSBzZXEoMSwgMywgYnkgPSAwLjEpCk9fbiA8LSAxMF5uX3ZhbHVlcy8xMF4yCk9fbjMgPC0gKDEwXm5fdmFsdWVzLzEwXjIpXjMKT19uX3B5dGhvbl8xIDwtIE9fbipweXRob25bcHl0aG9uJEFsZ29yaXRobSA9PSBhbGdvc1sxXSxdJFRpbWVbM10KT19uM19SXzEgPC0gT19uMypSW1IkQWxnb3JpdGhtID09IGFsZ29zWzFdLF0kVGltZVszXQpPX25fcHl0aG9uXzIgPC0gT19uKnB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzJdLF0kVGltZVszXQpPX24zX1JfMiA8LSBPX24zKlJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMl0sXSRUaW1lWzNdCnBsb3RfbHkoKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IFJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSwgeCA9IH5sb2coc3FydChuKSwgMTApLCB5ID0gflRpbWUsIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ21hdHJpeF9tdWx0aXBsaWNhdGlvbiBSJywKICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIGRhc2ggPSAnc29saWQnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJibHVlIiwgc3ltYm9sID0gJ2NpcmNsZScpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IFJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMl0sXSwgeCA9IH5sb2coc3FydChuKSwgMTApLCB5ID0gflRpbWUsIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ21hdHJpeF9pbnZlcnNpb24gUicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBkYXNoID0gJ2Rhc2hkb3QnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJibHVlIiwgc3ltYm9sID0gJ3NxdWFyZScpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IHB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzFdLF0sIHggPSB+bG9nKHNxcnQobiksIDEwKSwgeSA9IH5UaW1lLCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdtYXRyaXhfbXVsdGlwbGljYXRpb24gUHl0aG9uJywKICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAicmVkIiwgZGFzaCA9ICdzb2xpZCcpLAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gInJlZCIsIHN5bWJvbCA9ICdkb3QnKSkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBweXRob25bcHl0aG9uJEFsZ29yaXRobSA9PSBhbGdvc1syXSxdLCB4ID0gfmxvZyhzcXJ0KG4pLCAxMCksIHkgPSB+VGltZSwgCiAgICAgICAgICAgIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMrbWFya2VycycsIG5hbWUgPSAnbWF0cml4X2ludmVyc2lvbiBQeXRob24nLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJyZWQiLCBkYXNoID0gJ2Rhc2hkb3QnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJyZWQiLCBzeW1ib2wgPSAnc3F1YXJlJykpICU+JQogIGFkZF90cmFjZSh4ID0gbl92YWx1ZXMsIHkgPSBPX25fcHl0aG9uXzEsIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMnLAogICAgICAgICAgICBuYW1lID0gJ08obiknLCBsaW5lID0gbGlzdChjb2xvciA9ICdibGFjaycsIGRhc2ggPSAnZGFzaCcpKSAlPiUKICBhZGRfdHJhY2UoeCA9IG5fdmFsdWVzLCB5ID0gT19uM19SXzIsIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMnLAogICAgICAgICAgICBuYW1lID0gJ08obl4zKScsIGxpbmUgPSBsaXN0KGNvbG9yID0gJ2dyZWVuJywgZGFzaCA9ICdkYXNoJykpICU+JQogIGxheW91dCh0aXRsZSA9ICJFeGVjdXRpb24gVGltZSIsCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJsb2cobikiLCB0aWNrbW9kZSA9ICJhcnJheSIsIHRpY2t2YWxzID0gMDo2LCB0aWNrdGV4dCA9IGFzLmNoYXJhY3RlcigwOjYpKSwKICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIlRpbWUgKHMpIiksCiAgICAgICAgIGxlZ2VuZCA9IGxpc3QodGl0bGUgPSAiTGVnZW5kIiwgb3JpZW50YXRpb24gPSAiaCIsIHggPSAwLjMsIHkgPSAtMC4yKSkKcGxvdF9seSgpICU+JQogIGFkZF90cmFjZShkYXRhID0gUltSJEFsZ29yaXRobSA9PSBhbGdvc1sxXSxdLCB4ID0gfmxvZyhzcXJ0KG4pLCAxMCksIHkgPSB+TWVtb3J5LCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdtYXRyaXhfbXVsdGlwbGljYXRpb24gUicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBkYXNoID0gJ3NvbGlkJyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIHN5bWJvbCA9ICdjaXJjbGUnKSkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBSW1IkQWxnb3JpdGhtID09IGFsZ29zWzJdLF0sIHggPSB+bG9nKHNxcnQobiksIDEwKSwgeSA9IH5NZW1vcnksIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ21hdHJpeF9pbnZlcnNpb24gUicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBkYXNoID0gJ2Rhc2hkb3QnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJibHVlIiwgc3ltYm9sID0gJ3NxdWFyZScpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IHB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzFdLF0sIHggPSB+bG9nKHNxcnQobiksIDEwKSwgeSA9IH5NZW1vcnksIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ21hdHJpeF9tdWx0aXBsaWNhdGlvbiBQeXRob24nLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJyZWQiLCBkYXNoID0gJ3NvbGlkJyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAicmVkIiwgc3ltYm9sID0gJ2RvdCcpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IHB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzJdLF0sIHggPSB+bG9nKHNxcnQobiksIDEwKSwgeSA9IH5NZW1vcnksIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ21hdHJpeF9pbnZlcnNpb24gUHl0aG9uJywKICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAicmVkIiwgZGFzaCA9ICdkYXNoZG90JyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAicmVkIiwgc3ltYm9sID0gJ3NxdWFyZScpKSAlPiUKICBsYXlvdXQodGl0bGUgPSAiTWVtb3J5IHVzYWdlIiwKICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gImxvZyhuKSIsIHRpY2ttb2RlID0gImFycmF5IiwgdGlja3ZhbHMgPSAwOjYsIHRpY2t0ZXh0ID0gYXMuY2hhcmFjdGVyKDA6NikpLAogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiTWVtb3J5IChNQikiKSwKICAgICAgICAgbGVnZW5kID0gbGlzdCh0aXRsZSA9ICJMZWdlbmQiLCBvcmllbnRhdGlvbiA9ICJoIiwgeCA9IDAuMywgeSA9IC0wLjIpKQpgYGAKCgpJbnRlcmVzdGluZ2x5LCB0aGUgaW1wbGVtZW50YXRpb24gb2YgbWF0cml4IG11bHRpcGxpY2F0aW9uIGFuZCBpbnZlcnNpb24gaW4gUHl0aG9uIHNlZW1zIHRvIHNjYWxlIGxpbmVhcmx5IG9yIGV2ZW4gYmV0dGVyIHdpdGggJG4kLCBidXQgaXQgYmxvd3MgdXAgdG8gb3JkZXIgJE8obl4zKSQgZm9yIFIgZm9yIGxhcmdlciBtYXRyaWNlcy4gRm9yIHNtYWxsZXIgbWF0cmljZXMgb2Ygc2l6ZSBsZXNzIHRoYW4gJDEwMCBcdGltZXMgMTAwJCwgUiBzZWVtcyB0byBiZSBmYXN0ZXIgd2hpbGUgUHl0aG9uIGlzIG11Y2ggZmFzdGVyIGZvciBsYXJnZSAkbj0xMDAwJC4gQW4gaW1wb3J0YW50IG5vdGUgaXMgdGhhdCBtZW1vcnkgYWxzbyBzZWVtcyB0byBpbmNyZWFzZSBsaW5lYXJseSBmb3IgbGFyZ2VyIG1hdHJpY2VzIGluIFB5dGhvbiB3aGljaCBtYXkgYWxzbyBleHBsYWluIHdoeSB0aGUgZXhlY3V0aW9uIHRpbWUgaXMgbXVjaCBiZXR0ZXIgdGhhbiB0aGUgdGhlb3JldGljYWwgY29tcGxleGl0eSBpbiBweXRob24uCgoKIyMgTGluZWFyIHJlZ3Jlc3Npb24KClRoZSBvcmRpbmFyeSBsZWFzdCBzcXVhcmVzIChPTFMpIGVzdGltYXRlIG9mIGxpbmVhciByZWdyZXNzaW9uICRcaGF0e1xiZXRhfSQgZm9yICRcYmV0YSQgaXMgdGhlIHZlY3RvciB0aGF0IHNvbHZlcyB0aGUgY29uc3RyYWludCAkJFxhcmcgXG1pbl9cYmV0YSAoeSAtIFggXGJldGFeVCleMi4kJCBJZiB0aGUgZGF0YSBpcyBOb3JtYWxseSBkaXN0cmlidXRlZCBhcyBpbiB0aGUgW3NpbXVsYXRpb25dKCNzZXR1cCksIHRoZW4gaXQgaXMgd2VsbC1rbm93biB0aGF0ICRcaGF0e1xiZXRhfSQgaXMgZ2l2ZW4gYnkgCiQkXGhhdHtcYmV0YX0gPSAoWF5UIFgpXnstMX0gWF5UIHkuJCQKVGhlcmVmb3JlLCBPTFMgY29uc2lzdHMgc2ltcGx5IG9mIG1hdHJpeCBtdWx0aXBsaWNhdGlvbiBhbmQgaW52ZXJzaW9uLiBJbiBwc2V1ZG9jb2RlLCBpdCBjYW4gYmUgaW1wbGVtZW50ZWQgaW4gdGhlIGZvbGxvd2luZyBtYW5uZXIuCgoKYGBgcGxhaW50ZXh0CkZ1bmN0aW9uIGxpbmVhcl9yZWdyZXNzaW9uKFgsIHkpCiAgICAvLyBEaW1lbnNpb25zCiAgICBuIDwtIGxlbmd0aCBvZiBkYXRhIHkKICAgIHAgPC0gbnVtYmVyIG9mIGNvbHVtbnMgaW4gWAogICAgWF9iIDwtIEFwcGVuZCBjb2x1bW4gMSB0byBYIC8vIEluY2x1ZGUgYW4gaW50ZXJjZXB0CiAgICAKICAgIC8vIENvbXB1dGUgY29lZmZpY2llbnRzCiAgICBiZXRhX2hhdCA8LSAoWF9iLlQgQCBYX2IpXi0xIEAgKFhfYi5UIEAgeSkgCiAgICAKICAgIFJldHVybiBiZXRhX2hhdApFbmQgRnVuY3Rpb24KYGBgCgpUaGUgbWF0cml4IGF1Z21lbnRhdGlvbiBvcGVyYXRpb24gaXMgJE8obikkIHNpbmNlIHdlIGFyZSBhcHBlbmRpbmcgJG4kIHJvd3MuIFRoZSBtYXRyaXggbXVsdGlwbGljYXRpb24gYW5kIGludmVyc2lvbiBhcmUgdGhlIGRvbWluYXRpbmcgc3RlcHMgb2YgdGhpcyBhbGdvcml0aG0uIEFzIGV4cGxhaW5lZCBpbiB0aGUgcHJldmlvdXMgc2VjdGlvbiBvbiBtYXRyaWNlcywgdGhlIG1hdHJpeCBtdWx0aXBsaWNhdGlvbiBYX2IuVCBAIFhfYiBzdGVwIGlzICRPKG4gXHRpbWVzIChwKzEpXjIpJCB3aGlsZSB0aGUgbWF0cml4IGludmVyc2lvbiBpcyAkTygocCsxKV4zKSQsIGFsdGhvdWdoIHRoZXNlIG1hdHJpeCBvcGVyYXRpb25zIGFyZSBsaWtlbHkgaW1wbGVtZW50ZWQgbW9yZSBlZmZpY2llbnRseSBpbiBQeXRob24gYW5kIFIuIFRoZSBsYXN0IHN0ZXAgb2YgbXVsdGlwbHlpbmcgdGhlIG1hdHJpeCAoWF9iLlQgQCBYX2IpXi0xIHdpdGggdGhlIHZlY3RvciAoWF9iLlQgQCB5KSBpcyAkTygocCsxKV4yKSQuIFRoZXJlZm9yZSwgdGhlIG92ZXJhbGwgY29tcGxleGl0eSBpcyAkTyhwXjMpJCBvciAkTyhuIFx0aW1lcyAocCsxKV4yKSQgZGVwZW5kaW5nIG9uIHdoZXRoZXIgJHA8PG4kLiBXZSBjYW4gbmV2ZXIgaGF2ZSAkcD5uJCBpbiBsaW5lYXIgcmVncmVzc2lvbiBzaW5jZSB0aGUgY29lZmZpY2llbnRzIHdvdWxkIGJlY29tZSB1bmlkZW50aWZpYWJsZS4gSW4gdGhpcyBjYXNlIHNpbmNlIHdlIGtlZXAgJHA9MTAkIGFuZCB3ZSBpbmNyZWFzZSAkbiQgdG8gJG49MTBeNiQsIHRoZSBkb21pbmF0aW5nIHN0ZXAgaXMgdGhlIGZpcnN0IG1hdHJpeCBtdWx0aXBsaWNhdGlvbiBzdGVwIHdoaWNoIGlzICRPKG4pJC4gVGhpcyBzZXR1cCBpcyB0eXBpY2FsIG9mIGNsYXNzaWNhbCByZWdyZXNzaW9uIHdoZXJlIGEgcmVsYXRpdmVseSBzbWFsbCBudW1iZXIgb2YgY292YXJpYXRlcyBpcyB1c2VkLCBidXQgaW4gYSBoaWdoLWRpbWVuc2lvbmFsIHNldHRpbmcgd2UgY291bGQgaGF2ZSBsYXJnZSAkcCQgd2hpY2ggd291bGQgbWFrZSB0aGUgbWF0cml4IGludmVyc2lvbiBzdGVwIG1vcmUgY29zdGx5LgoKSW4gUHl0aG9uIHRoaXMgY2FuIGJlIGltcGxlbWVudGVkIGxpa2UgdGhlIGFsZ29yaXRobSBhYm92ZSBkaXJlY3RseSB1c2luZyBtYXRyaXggb3BlcmF0aW9ucyBvciB1c2luZyB0aGUgU2Npa2l0LUxlYXJuIGxpYnJhcnk6CmBgYHtweXRob24sIGV2YWwgPSBGQUxTRX0KIyBMaW5lYXIgcmVncmVzc2lvbiBmcm9tIGJhc2UKZGVmIGxpbl9yZWdfYmFzZShYLCB5KToKICAgIFhiID0gbnAuaHN0YWNrKFtucC5vbmVzKChYLnNoYXBlWzBdLCAxKSksIFhdKQogICAgYmV0YV9oYXQgPSBucC5saW5hbGcuaW52KFhiLlQgQCBYYikgQCAoWGIuVCBAIHkpCiAgICByZXR1cm4gYmV0YV9oYXQKICAKIyBza2xlYXJuLUxlYXJuIGxpbmVhciByZWdyZXNzaW9uCmRlZiBsaW5fcmVnX3NrbGVhcm4oWCwgeSk6CiAgICBtb2RlbCA9IExpbmVhclJlZ3Jlc3Npb24oKQogICAgbW9kZWwuZml0KFgsIHkpCiAgICBiZXRhX2hhdCA9IG5wLmhzdGFjayhbbW9kZWwuaW50ZXJjZXB0XywgbW9kZWwuY29lZl9dKQogICAgcmV0dXJuIGJldGFfaGF0CmBgYAoKYW5kIGluIFIgdGhpcyBpcyBhbHNvIGltcGxlbWVudGVkIGluIGJhc2UgUiB1c2luZyB0aGUgbG0oKSBmdW5jdGlvbjoKCmBgYHtyLCBldmFsID0gRkFMU0V9CiMgTGluZWFyIHJlZ3Jlc3Npb24gZnJvbSBiYXNlCmxpbl9yZWdfYmFzZSA8LSBmdW5jdGlvbihYLCB5KSB7CiAgWGIgPC0gY2JpbmQoMSwgWCkKICBiZXRhX2hhdCA8LSBzb2x2ZSh0KFhiKSAlKiUgWGIpICUqJSAodChYYikgJSolIHkpCiAgcmV0dXJuKGJldGFzKQp9CgojIExpbmVhciByZWdyZXNzaW9uIHVzaW5nIGxtKCkKbGluX3JlZ19wYWNrYWdlIDwtIGZ1bmN0aW9uKFgsIHkpIHsKICBtb2RlbCA8LSBsbSh5IH4gWCArIDApCiAgcmV0dXJuKGNvZWYobW9kZWwpKQp9CmBgYAoKCgpgYGB7ciBlY2hvPUZBTFNFfQphbGdvcyA8LSBjKCJsaW5lYXJfcmVncmVzc2lvbl9iYXNlIiwgImxpbmVhcl9yZWdyZXNzaW9uX3BhY2thZ2UiKQpuX3ZhbHVlcyA8LSBzZXEoMiwgNiwgYnkgPSAwLjEpCk9fbiA8LSAxMF5uX3ZhbHVlcy8xMF40Ck9fbl9weXRob25fMSA8LSBPX24qcHl0aG9uW3B5dGhvbiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSRUaW1lWzNdCk9fbl9SXzEgPC0gT19uKlJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSRUaW1lWzNdCk9fbl9weXRob25fMiA8LSBPX24qcHl0aG9uW3B5dGhvbiRBbGdvcml0aG0gPT0gYWxnb3NbMl0sXSRUaW1lWzNdCk9fbl9SXzIgPC0gT19uKlJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMl0sXSRUaW1lWzNdCnBsb3RfbHkoKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IFJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSwgeCA9IH5sb2cobiwgMTApLCB5ID0gflRpbWUsIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ2xpbmVhcl9yZWdyZXNzaW9uX3NjcmF0Y2ggUicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBkYXNoID0gJ3NvbGlkJyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIHN5bWJvbCA9ICdjaXJjbGUnKSkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBSW1IkQWxnb3JpdGhtID09IGFsZ29zWzJdLF0sIHggPSB+bG9nKG4sIDEwKSwgeSA9IH5UaW1lLCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdsaW5lYXJfcmVncmVzc2lvbl9iYXNlIFInLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJibHVlIiwgZGFzaCA9ICdkYXNoZG90JyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIHN5bWJvbCA9ICdzcXVhcmUnKSkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBweXRob25bcHl0aG9uJEFsZ29yaXRobSA9PSBhbGdvc1sxXSxdLCB4ID0gfmxvZyhuLCAxMCksIHkgPSB+VGltZSwgCiAgICAgICAgICAgIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMrbWFya2VycycsIG5hbWUgPSAnbGluZWFyX3JlZ3Jlc3Npb25fc2NyYXRjaCBQeXRob24nLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJyZWQiLCBkYXNoID0gJ3NvbGlkJyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAicmVkIiwgc3ltYm9sID0gJ2RvdCcpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IHB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzJdLF0sIHggPSB+bG9nKG4sIDEwKSwgeSA9IH5UaW1lLCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdsaW5lYXJfcmVncmVzc2lvbl9za2xlYXJuIFB5dGhvbicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gInJlZCIsIGRhc2ggPSAnZGFzaGRvdCcpLAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gInJlZCIsIHN5bWJvbCA9ICdzcXVhcmUnKSkgJT4lCiAgYWRkX3RyYWNlKHggPSBuX3ZhbHVlcywgeSA9IE9fbl9SXzEsIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMnLAogICAgICAgICAgICBuYW1lID0gJ08obiknLCBsaW5lID0gbGlzdChjb2xvciA9ICdibGFjaycsIGRhc2ggPSAnZGFzaCcpKSAlPiUKICBsYXlvdXQodGl0bGUgPSAiRXhlY3V0aW9uIFRpbWUiLAogICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAibG9nKG4pIiwgdGlja21vZGUgPSAiYXJyYXkiLCB0aWNrdmFscyA9IDA6NiwgdGlja3RleHQgPSBhcy5jaGFyYWN0ZXIoMDo2KSksCiAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJUaW1lIChzKSIpLAogICAgICAgICBsZWdlbmQgPSBsaXN0KHRpdGxlID0gIkxlZ2VuZCIsIG9yaWVudGF0aW9uID0gImgiLCB4ID0gMC4zLCB5ID0gLTAuMikpCm5fdmFsdWVzIDwtIHNlcSgyLCA2LCBieSA9IDAuMSkKT19uIDwtIDEwXm5fdmFsdWVzLzEwXjQKT19uX3B5dGhvbl8xIDwtIE9fbipweXRob25bcHl0aG9uJEFsZ29yaXRobSA9PSBhbGdvc1sxXSxdJE1lbW9yeVszXQpPX25fUl8xIDwtIE9fbipSW1IkQWxnb3JpdGhtID09IGFsZ29zWzFdLF0kTWVtb3J5WzNdCk9fbl9weXRob25fMiA8LSBPX24qcHl0aG9uW3B5dGhvbiRBbGdvcml0aG0gPT0gYWxnb3NbMl0sXSRNZW1vcnlbM10KT19uX1JfMiA8LSBPX24qUltSJEFsZ29yaXRobSA9PSBhbGdvc1syXSxdJE1lbW9yeVszXQpwbG90X2x5KCkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBSW1IkQWxnb3JpdGhtID09IGFsZ29zWzFdLF0sIHggPSB+bG9nKG4sIDEwKSwgeSA9IH5NZW1vcnksIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ2xpbmVhcl9yZWdyZXNzaW9uX3NjcmF0Y2ggUicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBkYXNoID0gJ3NvbGlkJyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIHN5bWJvbCA9ICdjaXJjbGUnKSkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBSW1IkQWxnb3JpdGhtID09IGFsZ29zWzJdLF0sIHggPSB+bG9nKG4sIDEwKSwgeSA9IH5NZW1vcnksIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ2xpbmVhcl9yZWdyZXNzaW9uX2Jhc2UgUicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBkYXNoID0gJ2Rhc2hkb3QnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJibHVlIiwgc3ltYm9sID0gJ3NxdWFyZScpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IHB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzFdLF0sIHggPSB+bG9nKG4sIDEwKSwgeSA9IH5NZW1vcnksIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ2xpbmVhcl9yZWdyZXNzaW9uX3NjcmF0Y2ggUHl0aG9uJywKICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAicmVkIiwgZGFzaCA9ICdzb2xpZCcpLAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gInJlZCIsIHN5bWJvbCA9ICdkb3QnKSkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBweXRob25bcHl0aG9uJEFsZ29yaXRobSA9PSBhbGdvc1syXSxdLCB4ID0gfmxvZyhuLCAxMCksIHkgPSB+TWVtb3J5LCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdsaW5lYXJfcmVncmVzc2lvbl9za2xlYXJuIFB5dGhvbicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gInJlZCIsIGRhc2ggPSAnZGFzaGRvdCcpLAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gInJlZCIsIHN5bWJvbCA9ICdzcXVhcmUnKSkgJT4lCiAgYWRkX3RyYWNlKHggPSBuX3ZhbHVlcywgeSA9IE9fbl9SXzIsIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMnLAogICAgICAgICAgICBuYW1lID0gJ08obiknLCBsaW5lID0gbGlzdChjb2xvciA9ICdibGFjaycsIGRhc2ggPSAnZGFzaCcpKSAlPiUKICBsYXlvdXQodGl0bGUgPSAiTWVtb3J5IHVzYWdlIiwKICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gImxvZyhuKSIsIHRpY2ttb2RlID0gImFycmF5IiwgdGlja3ZhbHMgPSAwOjYsIHRpY2t0ZXh0ID0gYXMuY2hhcmFjdGVyKDA6NikpLAogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiTWVtb3J5IChNQikiKSwKICAgICAgICAgbGVnZW5kID0gbGlzdCh0aXRsZSA9ICJMZWdlbmQiLCBvcmllbnRhdGlvbiA9ICJoIiwgeCA9IDAuMywgeSA9IC0wLjIpKQpgYGAKCgpMaW5lYXIgcmVncmVzc2lvbiBzZWVtcyB0byBzY2FsZSBsaW5lYXJseSBmb3IgZXhlY3V0aW9uIHRpbWUgaW4gUiwgYnV0IGV4ZWN1dGlvbiB0aW1lIHNlZW1zIGNvbnN0YW50IGFuZCBldmVuIHBlcnBsZXhpbmdseSBkZWNyZWFzZXMgZm9yIGxhcmdlICRuJCBpbiBQeXRob24uIFRoZSBpbXBsZW1lbnRhdGlvbiBmcm9tIHNjcmF0Y2ggaW4gYm90aCBQeXRob24gYW5kIFIgd2FzIGZhc3RlciB0aGFuIHRoZSBpbXBsZW1lbnRhdGlvbiBmcm9tIHNrbGVhcm4gaW4gUHl0aG9uIGFuZCBmcm9tIHRoZSBiYXNlIGZ1bmN0aW9uIGluIFIsIGFuZCBSIHdhcyBmYXN0ZXIgZm9yIGRhdGEgc2V0cyBsZXNzIHRoYW4gJG49MTBeNSQgd2hpbGUgUHl0aG9uIHNjYWxlZCBiZXR0ZXIgZm9yIGxhcmdlICRuPTEwXjYkLiBUaGUgZXhlY3V0aW9uIHRpbWUgb2YgbGluZWFyIHJlZ3Jlc3Npb24gZnJvbSBza2xlYXJuIGJsb3dzIHVwIGF0ICQxMF42JCBhbmQgaXMgY29uc2lkZXJhYmx5IHNsb3dlciB0aGFuIGFsbCBvdGhlciBpbXBsZW1lbnRhdGlvbnMuIFRoaXMgbWFrZXMgc2Vuc2Ugc2luY2Ugc2Npa2l0LWxlYXJuIGFuZCBsbSgpIGNvbnRhaW4gYWRkaXRpb25hbCBzdGVwcyB0aGFuIG15IHNpbXBsZSBjb2RlLCBzdWNoIGFzIHRyYW5zbGF0aW5nIHRoZSBmb3JtdWxhIHN5bnRheCBpbnRvIG1hdHJpeCBjYWxjdWxhdGlvbnMgYW5kIG1hbnkgb3RoZXIgb3B0aW9ucyBmb3IgbW9yZSBjb21wbGljYXRlZCB2ZXJzaW9ucyBvZiBsaW5lYXIgcmVncmVzc2lvbiwgYWx0aG91Z2ggSSBhbSBub3Qgc3VyZSB3aHkgaXQgc2NhbGVzIHNvIHBvb3JseSBmb3IgbGFyZ2UgJG4kLiBJbnRlcmVzdGluZ2x5LCBtZW1vcnkgc2NhbGVkIHJvdWdobHkgbGluZWFybHkgYW5kIGFsbCBhbGdvcml0aG1zIGluIGJvdGggUiBhbmQgUHl0aG9uIGhhZCBzaW1pbGFyIG1lbW9yeSBjdXJ2ZXMsIHdoaWNoIG1heSBleHBsYWluIHdoeSBleGVjdXRpb24gdGltZSBkaWQgbm90IHNlZW0gdG8gaW5jcmVhc2UgaW4gdGhlIGJhc2ljIGltcGxlbWVudGlvbiBpbiBQeXRob24uCgojIyBCb290c3RyYXAKClRoZSBib290c3RyYXAgYWxnb3JpdGhtIGlzIHBvcHVsYXIgaW4gc3RhdGlzdGljcyBiZWNhdXNlIGl0IGFsbG93cyBmb3IgdGhlIGVzdGltYXRpb24gb2YgdGhlIHByb3BlcnRpZXMgb2YgZ2VuZXJhbCBlc3RpbWF0b3JzIHVuZGVyIG1pbmltYWwgYXNzdW1wdGlvbnMgZXZlbiBpZiB0aGV5IGFyZSBub3QgYW5hbHl0aWNhbGx5IHRyYWN0YWJsZS4gVGhlIGlkZWEgb2YgdGhlIGJvb3RzdHJhcCBpcyB0byByZXNhbXBsZSBmcm9tIHRoZSBkYXRhIHRvIGNyZWF0ZSBuZXcgc3ludGhldGhpYyBkYXRhc2V0cyBhbmQgY29tcHV0ZSBhIG5ldyBlc3RpbWF0b3IgZm9yIGVhY2ggZGF0YXNldCwgZnJvbSB3aGljaCB0aGUgc2FtcGxpbmcgZGlzdHJpYnV0aW9uIG9mIHRoZSBlc3RpbWF0b3IgY2FuIGJlIGVzdGltYXRlZC4gVGhlIGJvb3RzdHJhcCB3YXMgaW5mbHVlbnRpYWwgaW4gbW92aW5nIHN0YXRpc3RpY3MgYXdheSBmcm9tIG1hdGhlbWF0aWNzIGFuZCB0b3dhcmRzIGNvbXB1dGF0aW9uIHNpbmNlIG9uZSBkb2VzIG5vdCBuZWVkIHRvIGtub3cgYW55dGhpbmcgYWJvdXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgZXN0aW1hdG9yIHRvIHBlcmZvcm0gdGhlIGFsZ29yaXRobS4gVGhlIG1ham9yIGFzc3VtcHRpb25zIG9mIHRoZSBzdGFuZGFyZCBib290c3RyYXAgYXJlIHRoYXQgdGhlIGRhdGEgYXJlIGluZGVwZW5kZW50IGFuZCBpZGVudGljYWxseSBkaXN0cmlidXRlZCBhbmQgdGhhdCB0aGUgZW1waXJpY2FsIGN1bXVsYXRpdmUgZGVuc2l0eSBmdW5jdGlvbiAoQ0RGKSBpcyBjbG9zZSB0byB0aGUgdHJ1ZSBDREYgb2YgdGhlIGRhdGEuIFRoZSBsYXR0ZXIgaXMgYXN5bXB0b3RpY2FsbHkganVzdGlmaWVkIGJ1dCBlbXBpcmljYWxseSB1bnRlc3RhYmxlLgoKSGVyZSBpcyBhIHBzZXVkb2NvZGUgZm9yIHRoZSBib290c3RyYXAgYWxnb3JpdGhtLgoKYGBgcGxhaW50ZXh0CkZ1bmN0aW9uIGJvb3RzdHJhcChkYXRhLCBzdGF0aXN0aWMsIEIsIGFscGhhKQogICAgbiA8LSBsZW5ndGggb2YgZGF0YQogICAgaWR4IDwtIHNhbXBsZSBCIHNldHMgb2YgaW5kaWNlcyB3aXRoIHJlcGxhY2VtZW50IAogICAgc2FtcGxlcyA8LSBleHRyYWN0IGRhdGEgd2l0aCBpbmRpY2VzIGlkeAogICAgc3RhdCA8LSBzb3J0KHN0YXRpc3RpYyhzYW1wbGVzKSkKICAgIGxvd2VyX0NJIDwtIGV4dHJhY3QgKDEwMCooMS1hbHBoYSkvMil0aCBzYW1wbGUKICAgIHVwcGVyX0NJIDwtIGV4dHJhY3QgKDEwMCooMS0oMS1hbHBoYSkvMil0aCBzYW1wbGUKICAgIFJldHVybiBsb3dlcl9DSSwgdXBwZXJfQ0kKRW5kIEZ1bmN0aW9uCmBgYAoKVGhlIGlkeCByZXNhbXBsaW5nIHN0ZXAgaXMgJE8obiBcdGltZXMgQikkIGFuZCBleHRyYWN0aW5nIHRoZSBzYW1wbGVzIGlzIGFsc28gJE8obiBcdGltZXMgQikkLiBGb3IgYSBsaW5lYXIgc3RhdGlzdGljIGxpa2UgYSBzYW1wbGUgbWVhbiwgdGhlIHN0YXRpc3RpYyBzdGVwIGlzIGFsc28gJE8obiBcdGltZXMgQikkLiBUaGUgc29ydGluZyBzdGVwIGRlcGVuZHMgb24gdGhlIGFsZ29yaXRobSB1c2VkLCBidXQgdGhlIG9uZSB1c2VkIGluIFB5dGhvbiBpcyBUaW1zb3J0IHdoaWNoIGhhcyAkTyhCIFxsb2cgKEIpKSQuIEFmdGVyIHRoYXQsIGNvbXB1dGluZyB0aGUgdHdvIHNhbXBsZSBxdWFudGlsZXMgb24gdGhlIHNvcnRlZCBkYXRhIGlzICRPKDEpJC4gSG93ZXZlciwgYSBwb3RlbnRpYWxseSBtb3JlIGVmZmljaWVudCB3YXkgb2YgZG9pbmcgdGhpcyBzdGVwIGlzIHVzaW5nIGFuIGltcGxlbWVudGF0aW9uIGZvciBzYW1wbGUgcXVhbnRpbGVzIHdoaWNoIG1heSBhdm9pZCBzb3J0aW5nIHRoZSBlbnRpcmUgZGF0YXNldC4gVGhlcmVmb3JlLCBzaW5jZSB3ZSBpbmNyZWFzZSAkQiQgYW5kIGtlZXAgJG49MTBeMyQgZml4ZWQsIHRoZSBkb21pbmF0aW5nIHN0ZXAgaXMgbGlrZWx5IHRoZSByZXNhbXBsaW5nL2V4dHJhY3Rpbmcvc3RhdGlzdGljIHN0ZXAsIHdoaWNoIGhhcyBjb21wbGV4aXR5ICRPKEIpJCwgYWx0aG91Z2ggaWYgd2UgdXNlIGEgbmFpdmUgc29ydGluZyBhbGdvcml0aG0gbGlrZSBidWJibGUgc29ydCB0aGlzIG1heSBiZWNvbWUgY29zdGx5LiBUaGVyZSBhcmUgb3RoZXIgbW9yZSBjb21wbGljYXRlZCBpbXBsZW1lbnRhdGlvbnMgb2YgdGhlIGJvb3RzdHJhcCB0aGF0IGRvIG5vdCB1c2Ugc2FtcGxlIHF1YW50aWxlcyBmb3IgdGhlIGNvbmZpZGVuY2UgaW50ZXJ2YWwuIEEgcG9wdWxhciBhcHByb2FjaCBpcyB0byB1c2UgYSBib290c3RyYXBwZWQgcGl2b3Qgd2l0aGluIGVhY2ggaXRlcmF0aW9uIG9mIHRoZSBhbGdvcml0aG0sIHRoZXJlYnkgbmVzdGluZyBhbiBhZGRpdGlvbmFsIGxvb3AgaW4gdGhlIGFsZ29yaXRobSB3aGljaCBpbmNyZWFzZXMgdGhlIGNvbXBsZXhpdHkgdG8gJE8oQl4yKSQuCgoKSW4gUHl0aG9uIHRoZSBzdGFuZGFyZCBib290c3RyYXAgY2FuIGJlIGltcGxlbWVudGVkIHVzaW5nIHRoZSBhbGdvcml0aG0gYWJvdmUgd2l0aCB2ZWN0b3JpemVkIG9wZXJhdGlvbnMgZm9yIGVmZmljaWVuY3ksIGFzIHdlbGwgYXMgdXNpbmcgdGhlIHNjaXB5LnN0YXRzIGxpYnJhcnkgYXMgZm9sbG93cy4KCmBgYHtweXRob24sIGV2YWwgPSBGQUxTRX0KIyBCb290c3RyYXAgZnJvbSBiYXNlCmRlZiBib290c3RyYXBfYmFzZShkYXRhLCBzdGF0aXN0aWMsIEIpOgogICAgbiA9IGxlbihkYXRhKQogICAgaWR4ID0gbnAucmFuZG9tLnJhbmRpbnQoMCwgbiwgKEIsIG4pKQogICAgc2FtcGxlcyA9IGRhdGFbaWR4XQogICAgc3RhdCA9IHN0YXRpc3RpYyhzYW1wbGVzKQogICAgY29uZmlkZW5jZV9pbnRlcnZhbCA9IG5wLnF1YW50aWxlKHN0YXQsIFswLjAyNSwgMC45NzVdKQogICAgcmV0dXJuIGNvbmZpZGVuY2VfaW50ZXJ2YWwKICAKIyBzY2lweS5zdGF0cyBib290c3RyYXAKZGVmIGJvb3RzdHJhcF9zY2lweShkYXRhLCBzdGF0aXN0aWMsIEIsIGNvbmZpZGVuY2VfbGV2ZWw9MC45NSk6CiAgICByZXMgPSBzdGF0cy5ib290c3RyYXAoKGRhdGEsKSwgc3RhdGlzdGljLCBuX3Jlc2FtcGxlcz1CLCBjb25maWRlbmNlX2xldmVsPWNvbmZpZGVuY2VfbGV2ZWwsIG1ldGhvZD0ncGVyY2VudGlsZScpCiAgICByZXR1cm4gcmVzLmNvbmZpZGVuY2VfaW50ZXJ2YWwKICAKZGVmIHNhbXBsZV9tZWFuKGRhdGEpOgogICAgcmV0dXJuIHN1bShkYXRhKS9sZW4oZGF0YSkKYGBgCgpJbiBSIHRoaXMgY2FuIGFsc28gYmUgaW1wbGVtZW50ZWQgdXNpbmcgYSB2ZWN0b3JpemVkIHZlcnNpb24gb3IgdXNpbmcgdGhlIGJvb3QgcGFja2FnZS4KCmBgYHtyLCBldmFsID0gRkFMU0V9CiMgQm9vdHN0cmFwIGZyb20gYmFzZQpib290c3RyYXBfYmFzZSA8LSBmdW5jdGlvbihkYXRhLCBzdGF0aXN0aWMsIEIpIHsKICBuIDwtIGxlbmd0aChkYXRhKQogIHJlc3VsdHMgPC0gcmVwbGljYXRlKEIsIHN0YXRpc3RpYyhzYW1wbGUoZGF0YSwgc2l6ZSA9IGxlbmd0aChkYXRhKSwgcmVwbGFjZSA9IFRSVUUpKSkKICBjaSA8LSBxdWFudGlsZShyZXN1bHRzLCBwcm9icyA9IGMoMC4wMjUsIDAuOTc1KSkKICByZXR1cm4oY2kpCn0KbWVhbl9mdW5jdGlvbiA8LSBmdW5jdGlvbihkYXRhKSB7CiAgbWVhbihkYXRhKQp9CgojIEJvb3RzdHJhcCBmcm9tIGJvb3QKYm9vdHN0cmFwX3BhY2thZ2UgPC0gZnVuY3Rpb24oZGF0YSwgc3RhdGlzdGljLCBSKSB7CiAgcmVzdWx0IDwtIGJvb3QoZGF0YSwgc3RhdGlzdGljID0gc3RhdGlzdGljLCBSID0gUikKICBjaSA8LSBib290LmNpKHJlc3VsdCwgdHlwZSA9ICJwZXJjIikkcGVyY2VudFs0OjVdCiAgcmV0dXJuKGNpKQp9CgptZWFuX2Jvb3QgPC0gZnVuY3Rpb24oZGF0YSwgaW5kaWNlcykgewogIG1lYW4oZGF0YVtpbmRpY2VzXSkKfQpgYGAKCgoKYGBge3IgZWNobz1GQUxTRX0KYWxnb3MgPC0gYygiYm9vdHN0cmFwX2Jhc2UiLCAiYm9vdHN0cmFwX3BhY2thZ2UiKQpuX3ZhbHVlcyA8LSBzZXEoMSwgNSwgYnkgPSAwLjEpCk9fbiA8LSAxMF5uX3ZhbHVlcy8xMF4zCk9fbl9weXRob25fMSA8LSBPX24qcHl0aG9uW3B5dGhvbiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSRUaW1lWzNdCk9fbl9SXzEgPC0gT19uKlJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSRUaW1lWzNdCk9fbl9weXRob25fMiA8LSBPX24qcHl0aG9uW3B5dGhvbiRBbGdvcml0aG0gPT0gYWxnb3NbMl0sXSRUaW1lWzNdCk9fbl9SXzIgPC0gT19uKlJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMl0sXSRUaW1lWzNdCnBsb3RfbHkoKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IFJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSwgeCA9IH5sb2cobi8xMCwgMTApLCB5ID0gflRpbWUsIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ2Jvb3RzdHJhcF9zY3JhdGNoIFInLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJibHVlIiwgZGFzaCA9ICdzb2xpZCcpLAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBzeW1ib2wgPSAnY2lyY2xlJykpICU+JQogIGFkZF90cmFjZShkYXRhID0gUltSJEFsZ29yaXRobSA9PSBhbGdvc1syXSxdLCB4ID0gfmxvZyhuLzEwLCAxMCksIHkgPSB+VGltZSwgCiAgICAgICAgICAgIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMrbWFya2VycycsIG5hbWUgPSAnYm9vdHN0cmFwX2Jvb3QgUicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBkYXNoID0gJ2Rhc2hkb3QnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJibHVlIiwgc3ltYm9sID0gJ3NxdWFyZScpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IHB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzFdLF0sIHggPSB+bG9nKG4vMTAsIDEwKSwgeSA9IH5UaW1lLCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdib290c3RyYXBfc2NyYXRjaCBQeXRob24nLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJyZWQiLCBkYXNoID0gJ3NvbGlkJyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAicmVkIiwgc3ltYm9sID0gJ2RvdCcpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IHB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzJdLF0sIHggPSB+bG9nKG4vMTAsIDEwKSwgeSA9IH5UaW1lLCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdib290c3RyYXBfc2NpcHkgUHl0aG9uJywKICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAicmVkIiwgZGFzaCA9ICdkYXNoZG90JyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAicmVkIiwgc3ltYm9sID0gJ3NxdWFyZScpKSAlPiUKICBhZGRfdHJhY2UoeCA9IG5fdmFsdWVzLCB5ID0gT19uX1JfMSwgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcycsCiAgICAgICAgICAgIG5hbWUgPSAnTyhuKScsIGxpbmUgPSBsaXN0KGNvbG9yID0gJ2JsYWNrJywgZGFzaCA9ICdkYXNoJykpICU+JQogIGxheW91dCh0aXRsZSA9ICJFeGVjdXRpb24gVGltZSIsCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJsb2coQikiLCB0aWNrbW9kZSA9ICJhcnJheSIsIHRpY2t2YWxzID0gMDo2LCB0aWNrdGV4dCA9IGFzLmNoYXJhY3RlcigwOjYpKSwKICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIlRpbWUgKHMpIiksCiAgICAgICAgIGxlZ2VuZCA9IGxpc3QodGl0bGUgPSAiTGVnZW5kIiwgb3JpZW50YXRpb24gPSAiaCIsIHggPSAwLjMsIHkgPSAtMC4yKSkKT19uIDwtIDEwXm5fdmFsdWVzLzEwXjMKT19uX3B5dGhvbl8xIDwtIE9fbipweXRob25bcHl0aG9uJEFsZ29yaXRobSA9PSBhbGdvc1sxXSxdJE1lbW9yeVszXQpPX25fUl8xIDwtIE9fbipSW1IkQWxnb3JpdGhtID09IGFsZ29zWzFdLF0kTWVtb3J5WzNdCk9fbl9weXRob25fMiA8LSBPX24qcHl0aG9uW3B5dGhvbiRBbGdvcml0aG0gPT0gYWxnb3NbMl0sXSRNZW1vcnlbM10KT19uX1JfMiA8LSBPX24qUltSJEFsZ29yaXRobSA9PSBhbGdvc1syXSxdJE1lbW9yeVszXQpwbG90X2x5KCkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBSW1IkQWxnb3JpdGhtID09IGFsZ29zWzFdLF0sIHggPSB+bG9nKG4vMTAsIDEwKSwgeSA9IH5NZW1vcnksIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ2Jvb3RzdHJhcF9zY3JhdGNoIFInLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJibHVlIiwgZGFzaCA9ICdzb2xpZCcpLAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBzeW1ib2wgPSAnY2lyY2xlJykpICU+JQogIGFkZF90cmFjZShkYXRhID0gUltSJEFsZ29yaXRobSA9PSBhbGdvc1syXSxdLCB4ID0gfmxvZyhuLzEwLCAxMCksIHkgPSB+TWVtb3J5LCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdib290c3RyYXBfYm9vdCBSJywKICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIGRhc2ggPSAnZGFzaGRvdCcpLAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBzeW1ib2wgPSAnc3F1YXJlJykpICU+JQogIGFkZF90cmFjZShkYXRhID0gcHl0aG9uW3B5dGhvbiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSwgeCA9IH5sb2cobi8xMCwgMTApLCB5ID0gfk1lbW9yeSwgCiAgICAgICAgICAgIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMrbWFya2VycycsIG5hbWUgPSAnYm9vdHN0cmFwX3NjcmF0Y2ggUHl0aG9uJywKICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAicmVkIiwgZGFzaCA9ICdzb2xpZCcpLAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gInJlZCIsIHN5bWJvbCA9ICdkb3QnKSkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBweXRob25bcHl0aG9uJEFsZ29yaXRobSA9PSBhbGdvc1syXSxdLCB4ID0gfmxvZyhuLzEwLCAxMCksIHkgPSB+TWVtb3J5LCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdib290c3RyYXBfc2NpcHkgUHl0aG9uJywKICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAicmVkIiwgZGFzaCA9ICdkYXNoZG90JyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAicmVkIiwgc3ltYm9sID0gJ3NxdWFyZScpKSAlPiUKICBsYXlvdXQodGl0bGUgPSAiTWVtb3J5IHVzYWdlIiwKICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gImxvZyhCKSIsIHRpY2ttb2RlID0gImFycmF5IiwgdGlja3ZhbHMgPSAwOjYsIHRpY2t0ZXh0ID0gYXMuY2hhcmFjdGVyKDA6NikpLAogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiTWVtb3J5IChNQikiKSwKICAgICAgICAgbGVnZW5kID0gbGlzdCh0aXRsZSA9ICJMZWdlbmQiLCBvcmllbnRhdGlvbiA9ICJoIiwgeCA9IDAuMywgeSA9IC0wLjIpKQpgYGAKCgpJbiB0aGUgcGxvdHMgYWJvdmUsIHdlIHNlZSB0aGF0IHRoZSBleGVjdXRpb24gdGltZSBmb3IgYm90aCB0aGUgcGFja2FnZSBhbmQgYmFzZSBpbXBsZW1lbnRhdGlvbnMgb2YgdGhlIGJvb3RzdHJhcCBpbiBQeXRob24gYmxvdyB1cCBmb3IgbGFyZ2UgJEI9MTBeNSQuIFNpbmNlIHRoZSBwbG90cyBhcmUgaW50ZXJhY3RpdmUsIEkgZW5jb3VyYWdlIHlvdSB0byByZW1vdmUgdGhlIFB5dGhvbiBkYXRhIHBvaW50cyBieSBjbGlraW5nIG9uIHRoZW0gaW4gdGhlIGxlZ2VuZC4gVGhlbiB3ZSBjYW4gc2VlIHRoYXQgdGhlIGltcGxlbWVudGF0aW9ucyBpbiBSIHNjYWxlIGxpbmVhcmx5IGFzIGV4cGVjdGVkIGZyb20gdGhlIHRoZW9yZXRpY2FsIGNvbXBsZXhpdHkuIFdlIGFsc28gc2VlIHRoYXQgbWVtb3J5IGluY3JlYXNlcyBleHRyZW1lbHkgZmFzdCBpbiBib3RoIFIgYW5kIFB5dGhvbiwgcmVhY2hpbmcgNEdCIGZvciAkQj0xMF41JCBpbiBQeXRob24gYW5kIDJHQiBpbiBSLiBSdW5uaW5nICQxMF41JCBpdGVyYXRpb25zIGlzIGxpa2VseSBvdmVya2lsbCBpbiBwcmFjdGljZSwgcGFydGljdWxhcmx5IGZvciBhIHNhbXBsZSBtZWFuIHdoaWNoIGhhcyByYXBpZCBjb252ZXJnZW5jZSBieSB0aGUgbGF3IG9mIGxhcmdlIG51bWJlcnMuIEluIGdlbmVyYWwsIHRoZSBNb250ZSBDYXJsbyBlcnJvciBmb3IgYSBzaW1wbGUgZXN0aW1hdG9yIG9mdGVuIGJlY29tZXMgbXVjaCBzbWFsbGVyIHRoZW4gdGhlIHNhbXBsaW5nIHZhcmlhbmNlIGFib3ZlICQxMF4zJCBvciAkMTBeNCQgaXRlcmF0aW9ucy4KCgojIyBNQ01DCgoKTWFya292IGNoYWluIE1vbnRlIENhcmxvIGFsZ29yaXRobXMgKE1DTUMpIGFyZSBjZW50cmFsIG1ldGhvZHMgdG8gQmF5ZXNpYW4gc3RhdGlzdGljcyBzaW5jZSB0aGV5IGFsbG93IHVzIHRvIGVzdGltYXRlIHRoZSBwb3N0ZXJpb3IgZGlzdHJpYnV0aW9uIG9mIHBhcmFtZXRlcnMgd2l0aCBnZW5lcmFsIHByaW9ycyBhbmQgbGlrZWxpaG9vZHMuIEJheWVzIHRoZW9yZW0gc2hvd3MgaG93IHRvIG9idGFpbiBhIHBvc3RlcmlvciBkaXN0cmlidXRpb24gYnkgbXVsdGlwbHlpbmcgdGhlIHByaW9yIHdpdGggdGhlIGxpa2VsaWhvb2QgYW5kIGRpdmlkaW5nIGJ5IHRoZSBtYXJnaW5hbCBkaXN0cmlidXRpb24gd2hpY2ggaW52b2x2ZXMgdGFraW5nIGFuIGludGVncmFsLiBJbiBhIGZldyBjYXNlcyBsaWtlIHRoZSBOb3JtYWwgZGlzdHJpYnV0aW9uLCB0aGUgcG9zdGVyaW9yIGRpc3RyaWJ1dGlvbiBpcyBvZiB0aGUgc2FtZSBmYW1pbHkgYXMgdGhlIHByaW9yIGFuZCBhIGNsb3NlZCBmb3JtIGlzIGF2YWlsYWJsZS4gSG93ZXZlciwgZm9yIGFyYml0cmFyeSBwcmlvcnMgYW5kIGxpa2VsaWhvb2RzIHRoaXMgaW50ZWdyYWwgaXMgdHlwaWNhbGx5IHZlcnkgZGlmZmljdWx0IG9yIGludHJhY3RhYmxlLCBhbmQgbnVtZXJpY2FsIG1ldGhvZHMgZm9yIGNvbXB1dGluZyBpbnRlZ3JhbHMgb24gYSBncmlkIHN1ZmZlciBmcm9tIHRoZSBjdXJzZSBvZiBkaW1lbnNpb25hbGl0eS4gTUNNQyBhbGdvcml0aG1zIHN1Y2ggYXMgdGhlIE1ldHJvcG9saXMtSGFzdGluZ3MgYWxnb3JpdGhtIGFwcHJveGltYXRlcyB0aGUgcG9zdGVyaW9yIGRpc3RyaWJ1dGlvbiBieSBpdGVyYXRpdmVseSBnZW5lcmF0aW5nIHNhbXBsZXMgZnJvbSBhIHByb3Bvc2FsIGRpc3RyaWJ1dGlvbiBhbmQgY29tcHV0aW5nIGFuIGFjY2VwdGFuY2UgcmF0aW8gdGhhdCBhbGxvd3MgdGhlIHNhbXBsZSB0byBhcHByb2FjaCB0aGUgcG9zdGVyaW9yIGRpc3RyaWJ1dGlvbi4gVGhlIGFjY2VwdGFuY2UgcmF0aW8gZGVwZW5kcyBvbmx5IG9uIHRoZSBjdXJyZW50IGFuZCBwcmV2aW91cyBzYW1wbGUsIGFuZCBpdCBhdm9pZHMgaGF2aW5nIHRvIGNvbXB1dGUgdGhlIG5vcm1hbGl6YXRpb24gY29uc3RhbnQgc2luY2UgaXQgY2FuY2VscyBvdXQgaW4gdGhlIHJhdGlvLiBBIHNpbXBsZSBwcm9wb3NhbCBmdW5jdGlvbiB1c2VzIHRoZSBHYXVzc2lhbiBkaXN0cmlidXRpb24gYXQgdGhlIGN1cnJlbnQgcG9pbnQsIHdoaWxlIG1vcmUgb3RoZXIgTUNNQyBhbGdvcml0aG1zIGxpa2UgSGFtaWx0b25pYW4gTW9udGUgQ2FybG8gdXNlIG1vcmUgY29tcGxpY2F0ZWQgcHJvcG9zYWwgZGlzdHJpYnV0aW9ucyBieSBsZXZlcmFnaW5nIEhhbWlsdG9uaWFuIGR5bmFtaWNzLiBNQ01DIGFsZ29yaXRobXMgYXJlIHdlbGwganVzdGlmaWVkIHRoZW9yZXRpY2FsbHksIGFuZCB1bmxpa2UgdGhlIGJvb3RzdHJhcCBpdCBpcyBwb3NzaWJsZSB0byBhc3Nlc3MgdGhlIGNvbnZlcmdlbmNlIG9mIHRoZSBhbGdvcml0aG0gYnkgcnVubmluZyBtdWx0aXBsZSBjaGFpbnMgYW5kIGV4YW1pbmluZyB3aGV0aGVyIHRoZSBjaGFpbnMgY29udmVyZ2UgdG8gdGhlIHNhbWUgZGlzdHJpYnV0aW9uLCBhbHRob3VnaCBJIGRvIG5vdCBjb25zaWRlciB0aGlzIGhlcmUuCgpIZXJlIGlzIGEgc2ltcGxlIHBzZXVkb2NvZGUgaW1wbGVtZW50YXRpb24gb2YgdGhlIE1ldHJvcG9saXMgSGFzdGluZ3MgYWxnb3JpdGhtIGZvciB0aGUgc2FtZSBsaW5lYXIgcmVncmVzc2lvbiBzZXR0aW5nIGFzIGJlZm9yZS4gTGluZWFyIHJlZ3Jlc3Npb24gaXMgYWN0dWFsbHkgY29uanVnYXRlIHdpdGggTm9ybWFsIGRpc3RyaWJ1dGlvbnMgd2hpY2ggSSB1c2UgaGVyZSBmb3Igc2ltcGxpY2l0eSwgc28gTWV0cm9wb2xpcy1IYXN0aW5ncyBvciBldmVuIGFueSBhcHByb3hpbWF0aW9uIGlzIG5vdCBzdHJpY3RseSBuZWNlc3NhcnkgaW4gdGhpcyBzZXR0aW5nLgoKYGBgcGxhaW50ZXh0CkZ1bmN0aW9uIG1ldHJvcG9saXNfaGFzdGluZ3MoWCwgeSwgQiwgYmV0YV8wLCBwcm9wb3NhbF9zZCwgc2lnbWEpCiAgICAvLyBJbmNsdWRlIGFuIGludGVyY2VwdAogICAgWF9iIDwtIEFwcGVuZCBjb2x1bW4gMSB0byBYCiAgICAKICAgIC8vIEluaXRpYWxpemUgcGFyYW1ldGVycwogICAgY3VycmVudF9iZXRhIDwtIGJldGFfMAogICAgc2FtcGxlcyA8LSBjdXJyZW50X2JldGEKICAgIFhiIDwtIFhfYiBkb3QgY3VycmVudF9iZXRhCiAgICBjdXJyZW50X2xpa2VsaWhvb2QgPC0gc3VtKGxvZyhOKHl8WGIsc2lnbWEpKQogICAgY3VycmVudF9wcmlvciA8LSBzdW0obG9nKE4oY3VycmVudF9iZXRhfG11MCxzaWdtYTApKSkKICAgIAogICAgLy8gU2FtcGxpbmcKICAgIEZvciBpIGZyb20gMSB0byBCCiAgICAgICAgLy8gUHJvcG9zYWwKICAgICAgICBwcm9wb3NlZF9iZXRhIDwtIHNhbXBsZSBOKGN1cnJlbnRfYmV0YSxwcm9wb3NhbF9zZCkKICAgICAgICBYYl9wcm9wb3NlZCA8LSBYX2IgZG90IHByb3Bvc2VkX2JldGEKICAgICAgICBwcm9wb3NlZF9saWtlbGlob29kIDwtIHN1bShsb2coTih5fFhiX3Byb3Bvc2VkLHNpZ21hKSkKICAgICAgICBwcm9wb3NlZF9wcmlvciA8LSBzdW0obG9nKE4ocHJvcG9zZWRfYmV0YXxtdTAsc2lnbWEwKSkpCiAgICAgICAgCiAgICAgICAgLy8gQWNjZXB0L3JlamVjdCBuZXcgYmV0YQogICAgICAgIHBfYWNjZXB0IDwtIGV4cChwcm9wb3NlZF9saWtlbGlob29kICsgcHJvcG9zZWRfcHJpb3IgLSBjdXJyZW50X2xpa2VsaWhvb2QgLSBjdXJyZW50X3ByaW9yKQogICAgICAgIFUgPC0gc2FtcGxlIFVuaWYoMCwxKQogICAgICAgIElmIFUgPCBwX2FjY2VwdAogICAgICAgICAgICBjdXJyZW50X2JldGEgPC0gcHJvcG9zZWRfYmV0YQogICAgICAgICAgICBjdXJyZW50X2xpa2VsaWhvb2QgPC0gcHJvcG9zZWRfbGlrZWxpaG9vZAogICAgICAgICAgICBjdXJyZW50X3ByaW9yIDwtIHByb3Bvc2VkX3ByaW9yCiAgICAgICAgCiAgICAgICAgQXBwZW5kIGN1cnJlbnRfYmV0YSB0byBzYW1wbGVzCiAgICAKICAgIFJldHVybiBzYW1wbGVzCkVuZCBGdW5jdGlvbgpgYGAKCgpBdWdtZW50aW5nIHRoZSBtYXRyaXggaXMgJE8obikkLCB0aGUgbWF0cml4IGRvdCBwcm9kdWN0cyBmcm9tIGxpbmVhciByZWdyZXNzaW9uIGlzICRPKG4gXHRpbWVzIHApJCwgY29tcHV0aW5nIHRoZSBsb2ctbGlrZWxpaG9vZCBpcyAkTyhuKSQgYW5kIGNvbXB1dGluZyB0aGUgcHJpb3IgaXMgJE8ocCkkLiBUaGVyZWZvcmUsIHJ1bm5pbmcgdGhlIGxvb3AgZm9yIEIgaXRlcmF0aW9ucyBoYXMgY29tcGxleGl0eSAkTyhuIFx0aW1lcyBwIFx0aW1lcyBCKSQsIG9yIGtlZXBpbmcgYWxsIGVsc2UgZml4ZWQgaXQgaXMgJE8oQikkLiBUaGlzIHNlZW1zIGdyZWF0IHNpbmNlIHdlIHNlZW0gdG8gaGF2ZSBhdm9pZGVkIGN1cnNlIG9mIGRpbWVuc2lvbmFsaXR5LCBidXQgaXQgaGlkZXMgdGhlIGltcG9ydGFudCBmYWN0IHRoYXQgdGhpcyBhbGdvcml0aG0gbXVzdCBiZSBydW4gdW50aWwgaXQgY29udmVyZ2VzIHRvIHRoZSBwb3N0ZXJpb3IgZGlzdHJpYnV0aW9uLiBUaGlzIGlzIGFjdHVhbGx5IGhpZ2hseSBkZXBlbmRlbnQgb24gZGltZW5zaW9uIGJlY2F1c2UgdGhlIHJhbmRvbSB3YWxrIHdpbGwgbmVlZCB0byBydW4gbXVjaCBsb25nZXIgaW4gaGlnaCBkaW1lbnNpb25zIHRvIGV4cGxvcmUgdGhlIGVudGlyZSBzcGFjZSBvZiB0aGUgcG9zdGVyaW9yIGRpc3RyaWJ1dGlvbi4gU29tZSBhbGdvcml0aG1zIGxpa2UgSGFtaWx0b25pYW4gTW9udGUgQ2FybG8gaW1wbGVtZW50ZWQgaW4gU3RhbiBpbiBib3RoIFIgYW5kIFB5dGhvbiBoZWxwIGFkZHJlc3MgdGhpcyBpc3N1ZSB0aG91Z2ggbW9yZSBjb21wbGljYXRlZCBwcm9wb3NhbCBmdW5jdGlvbnMgdGhhdCByZWR1Y2UgdGhlIGF1dG9jb3JyZWxhdGlvbiBiZXR3ZWVuIHByb3Bvc2Fscy4KClRoZSBNZXRyb3BvbGlzLUhhc3RpbmdzIGFsZ29yaXRobSBjYW4gYmUgaW1wbGVtZW50ZWQgaW4gUHl0aG9uIGFzIGZvbGxvd3MuCgpgYGB7cHl0aG9uLCBldmFsID0gRkFMU0V9CiMgTWV0cm9wb2xpc19oYXN0aW5ncyBmcm9tIHNjcmF0Y2gKZGVmIG1ldHJvcG9saXNfaGFzdGluZ3MoWCwgeSwgbnVtX3NhbXBsZXMsIGJldGFfMD1ucC56ZXJvcygxMSksIHByb3Bvc2FsX3NkPTEsIHNpZ21hPTEpOgogICAgWCA9IG5wLmhzdGFjayhbbnAub25lcygoWC5zaGFwZVswXSwgMSkpLCBYXSkKICAgIGN1cnJlbnRfYmV0YSA9IGJldGFfMAogICAgc2FtcGxlcyA9IFtjdXJyZW50X2JldGFdCiAgICAKICAgIFhiID0gWC5kb3QoY3VycmVudF9iZXRhKQogICAgY3VycmVudF9saWtlbGlob29kID0gbnAuc3VtKHN0YXRzLm5vcm0ubG9ncGRmKHksIFhiLCBzaWdtYSkpCiAgICBjdXJyZW50X3ByaW9yID0gbnAuc3VtKHN0YXRzLm5vcm0ubG9ncGRmKGN1cnJlbnRfYmV0YSwgMCwgMTApKQoKICAgIGZvciBpIGluIHJhbmdlKG51bV9zYW1wbGVzKToKICAgICAgICBwcm9wb3NlZF9iZXRhID0gbnAucmFuZG9tLm5vcm1hbChjdXJyZW50X2JldGEsIHByb3Bvc2FsX3NkKQogICAgICAgIFhiX3Byb3Bvc2VkID0gWC5kb3QocHJvcG9zZWRfYmV0YSkKICAgICAgICBwcm9wb3NlZF9saWtlbGlob29kID0gbnAuc3VtKHN0YXRzLm5vcm0ubG9ncGRmKHksIFhiX3Byb3Bvc2VkLCBzaWdtYSkpCiAgICAgICAgcHJvcG9zZWRfcHJpb3IgPSBucC5zdW0oc3RhdHMubm9ybS5sb2dwZGYocHJvcG9zZWRfYmV0YSwgMCwgMTApKQogICAgICAgIAogICAgICAgIHBfYWNjZXB0ID0gbnAuZXhwKChwcm9wb3NlZF9saWtlbGlob29kICsgcHJvcG9zZWRfcHJpb3IpIC0gKGN1cnJlbnRfbGlrZWxpaG9vZCArIGN1cnJlbnRfcHJpb3IpKQogICAgICAgIAogICAgICAgIGlmIG5wLnJhbmRvbS5yYW5kKCkgPCBwX2FjY2VwdDoKICAgICAgICAgICAgY3VycmVudF9iZXRhID0gcHJvcG9zZWRfYmV0YQogICAgICAgICAgICBjdXJyZW50X2xpa2VsaWhvb2QgPSBwcm9wb3NlZF9saWtlbGlob29kCiAgICAgICAgICAgIGN1cnJlbnRfcHJpb3IgPSBwcm9wb3NlZF9wcmlvcgogICAgICAgIAogICAgICAgIHNhbXBsZXMuYXBwZW5kKGN1cnJlbnRfYmV0YSkKICAgIAogICAgcmV0dXJuIG5wLmFycmF5KHNhbXBsZXMpCmBgYAoKVGhlIE1ldHJvcG9saXMtSGFzdGluZ3MgYWxnb3JpdGhtIGNhbiBiZSBpbXBsZW1lbnRlZCBpbiBhIHNpbWlsYXIgZm9ybSBpbiBSIGFzIGZvbGxvd3MuIAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KIyBNZXRyb3BvbGlzX2hhc3RpbmdzIGZvciBsaW5lYXIgcmVncmVzc2lvbiBmcm9tIGJhc2UgUgptZXRyb3BvbGlzX2hhc3RpbmdzIDwtIGZ1bmN0aW9uKFgsIHksIG51bV9zYW1wbGVzLCBiZXRhXzAgPSByZXAoMCwxMSksIHByb3Bvc2FsX3NkPTEsIHNpZ21hPTEpIHsKICBYIDwtIGNiaW5kKDEsIFgpCiAgY3VycmVudF9iZXRhIDwtIGJldGFfMAogIHNhbXBsZXMgPC0gbGlzdChjdXJyZW50X2JldGEpCiAgICAKICBYYiA8LSBYICUqJSBjdXJyZW50X2JldGEKICBjdXJyZW50X2xpa2VsaWhvb2QgPC0gc3VtKGRub3JtKHksIFhiLCBzaWdtYSwgbG9nID0gVFJVRSkpCiAgY3VycmVudF9wcmlvciA8LSBzdW0oZG5vcm0oY3VycmVudF9iZXRhLCAwLCAxMCwgbG9nID0gVFJVRSkpCiAgCiAgZm9yIChpIGluIDE6bnVtX3NhbXBsZXMpIHsKICAgIHByb3Bvc2VkX2JldGEgPC0gbXZybm9ybSgxLCBjdXJyZW50X2JldGEsIGRpYWcocmVwKHByb3Bvc2FsX3NkLCBsZW5ndGgoYmV0YV8wKSkpKQogICAgWGJfcHJvcG9zZWQgPC0gWCAlKiUgcHJvcG9zZWRfYmV0YQogICAgcHJvcG9zZWRfbGlrZWxpaG9vZCA8LSBzdW0oZG5vcm0oeSwgWGJfcHJvcG9zZWQsIHNpZ21hLCBsb2cgPSBUUlVFKSkKICAgIHByb3Bvc2VkX3ByaW9yIDwtIHN1bShkbm9ybShwcm9wb3NlZF9iZXRhLCAwLCAxMCwgbG9nID0gVFJVRSkpCiAgICAKICAgIHBfYWNjZXB0IDwtIGV4cCgocHJvcG9zZWRfbGlrZWxpaG9vZCArIHByb3Bvc2VkX3ByaW9yKSAtIChjdXJyZW50X2xpa2VsaWhvb2QgKyBjdXJyZW50X3ByaW9yKSkKICAgIAogICAgaWYgKHJ1bmlmKDEpIDwgcF9hY2NlcHQpIHsKICAgICAgY3VycmVudF9iZXRhIDwtIHByb3Bvc2VkX2JldGEKICAgICAgY3VycmVudF9saWtlbGlob29kIDwtIHByb3Bvc2VkX2xpa2VsaWhvb2QKICAgICAgY3VycmVudF9wcmlvciA8LSBwcm9wb3NlZF9wcmlvcgogICAgfQogICAgCiAgICBzYW1wbGVzW1tpICsgMV1dIDwtIGN1cnJlbnRfYmV0YQogIH0KICAKICAKICByZXR1cm4oZG8uY2FsbChyYmluZCwgc2FtcGxlcykpCn0KYGBgCgoKQm90aCBSIGFuZCBQeXRob24gYWxzbyBzdXBwb3J0IGludGVyZmFjZXMgZm9yIHRoZSBTdGFuIHByb2dyYW1taW5nIGxhbmd1YWdlIHVzaW5nIGNtZHN0YW5weSBpbiBQeXRob24gYW5kIFJzdGFuIGluIFIuIFN0YW4gcGVyZm9ybXMgZWZmaWNpZW50IEhhbWlsdG9uaWFuIE1vbnRlIENhcmxvIHdpdGggYSBOby1VLVR1cm4gc2FtcGxlci4gVGhlIG5vdGF0aW9uIGluIFN0YW4gaXMgcHJvYmFiaWxpc3RpYyB3aGljaCBpcyBjb252ZW5pZW50IGZvciBzdGF0aXN0aWNhbCBtb2RlbCBidWlsZGluZywgYW5kIHRoZSBjb21wdXRhdGlvbnMgYXJlIGNvbXBpbGVkIGVmZmljaWVudGx5IGluIEMrKy4gSGVyZSBpcyBhbiBleGFtcGxlIG9mIGEgbm9uLWNvbmp1Z2F0ZSBpbXBsZW1lbnRhdGlvbiBvZiBsaW5lYXIgcmVncmVzc2lvbiB3aXRoIGEgQ2F1Y2h5IHByaW9yIG9uIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24uCgpgYGB7c3RhbiBvdXRwdXQudmFyPSJtb2RlbCIsIGV2YWwgPSBGQUxTRX0KZGF0YSB7CmludCBOOyAvLyBTYW1wbGUgc2l6ZQppbnQgSzsgLy8gTnVtYmVyIG9mIHByZWRpY3RvcnMKbWF0cml4W04sIEtdIFg7IC8vIENvdmFyaWF0ZXMKdmVjdG9yW05dIHk7IC8vIE91dGNvbWUKfQpwYXJhbWV0ZXJzIHsKdmVjdG9yW0tdIGJldGE7CnJlYWwgc2lnbWE7Cn0KCm1vZGVsIHsKLy8gUHJpb3JzCmJldGEgfiBub3JtYWwoMCwgMTApOwpzaWdtYSB+IGNhdWNoeSgwLCA1KTsKCi8vIExpa2VsaWhvb2QKeSB+IG5vcm1hbChYICogYmV0YSAsIHNpZ21hKTsKfQpgYGAKCgoKYGBge3IgZWNobz1GQUxTRX0KYWxnb3MgPC0gYygiTWV0cm9wb2xpc19IYXN0aW5ncyIsICJNQ01DX3N0YW4iKQpuX3ZhbHVlcyA8LSBzZXEoMSwgNSwgYnkgPSAwLjEpCk9fbiA8LSAxMF5uX3ZhbHVlcy8xMF4zCk9fbl9weXRob25fMSA8LSBPX24qcHl0aG9uW3B5dGhvbiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSRUaW1lWzNdCk9fbl9SXzEgPC0gT19uKlJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSRUaW1lWzNdCk9fbl9weXRob25fMiA8LSBPX24qcHl0aG9uW3B5dGhvbiRBbGdvcml0aG0gPT0gYWxnb3NbMl0sXSRUaW1lWzNdCk9fbl9SXzIgPC0gT19uKlJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMl0sXSRUaW1lWzNdCnBsb3RfbHkoKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IFJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSwgeCA9IH5sb2cobi8xMCwgMTApLCB5ID0gflRpbWUsIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ01ldHJvcG9saXNfSGFzdGluZ3MgUicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBkYXNoID0gJ3NvbGlkJyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIHN5bWJvbCA9ICdjaXJjbGUnKSkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBSW1IkQWxnb3JpdGhtID09IGFsZ29zWzJdLF0sIHggPSB+bG9nKG4vMTAsIDEwKSwgeSA9IH5UaW1lLCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdNQ01DX3N0YW4gUicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBkYXNoID0gJ2Rhc2hkb3QnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJibHVlIiwgc3ltYm9sID0gJ3NxdWFyZScpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IHB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzFdLF0sIHggPSB+bG9nKG4vMTAsIDEwKSwgeSA9IH5UaW1lLCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdNZXRyb3BvbGlzX0hhc3RpbmdzIFB5dGhvbicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gInJlZCIsIGRhc2ggPSAnc29saWQnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJyZWQiLCBzeW1ib2wgPSAnZG90JykpICU+JQogIGFkZF90cmFjZShkYXRhID0gcHl0aG9uW3B5dGhvbiRBbGdvcml0aG0gPT0gYWxnb3NbMl0sXSwgeCA9IH5sb2cobi8xMCwgMTApLCB5ID0gflRpbWUsIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ01DTUNfc3RhbiBQeXRob24nLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJyZWQiLCBkYXNoID0gJ2Rhc2hkb3QnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJyZWQiLCBzeW1ib2wgPSAnc3F1YXJlJykpICU+JQogIGFkZF90cmFjZSh4ID0gbl92YWx1ZXMsIHkgPSBPX25fUl8xLCB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzJywKICAgICAgICAgICAgbmFtZSA9ICdPKG4pJywgbGluZSA9IGxpc3QoY29sb3IgPSAnYmxhY2snLCBkYXNoID0gJ2Rhc2gnKSkgJT4lCiAgYWRkX3RyYWNlKHggPSBuX3ZhbHVlcywgeSA9IE9fbl9weXRob25fMSwgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcycsCiAgICAgICAgICAgIG5hbWUgPSAnTyhuKScsIGxpbmUgPSBsaXN0KGNvbG9yID0gJ2JsYWNrJywgZGFzaCA9ICdkYXNoJyksIHNob3dsZWdlbmQgPSBGQUxTRSkgJT4lCiAgIyBhZGRfdHJhY2UoeCA9IG5fdmFsdWVzLCB5ID0gT19uX1JfMiwgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcycsCiAgIyAgICAgICAgICAgbmFtZSA9ICdPKG4pJywgbGluZSA9IGxpc3QoY29sb3IgPSAnYmxhY2snLCBkYXNoID0gJ2Rhc2gnKSwgc2hvd2xlZ2VuZCA9IEZBTFNFKSAlPiUKICAjIGFkZF90cmFjZSh4ID0gbl92YWx1ZXMsIHkgPSBPX25fcHl0aG9uXzIsIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMnLAogICMgICAgICAgICAgIG5hbWUgPSAnTyhuKScsIGxpbmUgPSBsaXN0KGNvbG9yID0gJ2JsYWNrJywgZGFzaCA9ICdkYXNoJyksIHNob3dsZWdlbmQgPSBGQUxTRSkgJT4lCiAgbGF5b3V0KHRpdGxlID0gIkV4ZWN1dGlvbiBUaW1lIiwKICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gImxvZyhCKSIsIHRpY2ttb2RlID0gImFycmF5IiwgdGlja3ZhbHMgPSAwOjYsIHRpY2t0ZXh0ID0gYXMuY2hhcmFjdGVyKDA6NikpLAogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiVGltZSAocykiKSwKICAgICAgICAgbGVnZW5kID0gbGlzdCh0aXRsZSA9ICJMZWdlbmQiLCBvcmllbnRhdGlvbiA9ICJoIiwgeCA9IDAuMywgeSA9IC0wLjIpKQpwbG90X2x5KCkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBSW1IkQWxnb3JpdGhtID09IGFsZ29zWzFdLF0sIHggPSB+bG9nKG4vMTAsIDEwKSwgeSA9IH5NZW1vcnksIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ01ldHJvcG9saXNfSGFzdGluZ3MgUicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBkYXNoID0gJ3NvbGlkJyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIHN5bWJvbCA9ICdjaXJjbGUnKSkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBSW1IkQWxnb3JpdGhtID09IGFsZ29zWzJdLF0sIHggPSB+bG9nKG4vMTAsIDEwKSwgeSA9IH5NZW1vcnksIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ01DTUNfc3RhbiBSJywKICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIGRhc2ggPSAnZGFzaGRvdCcpLAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBzeW1ib2wgPSAnc3F1YXJlJykpICU+JQogIGFkZF90cmFjZShkYXRhID0gcHl0aG9uW3B5dGhvbiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSwgeCA9IH5sb2cobi8xMCwgMTApLCB5ID0gfk1lbW9yeSwgCiAgICAgICAgICAgIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMrbWFya2VycycsIG5hbWUgPSAnTWV0cm9wb2xpc19IYXN0aW5ncyBQeXRob24nLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJyZWQiLCBkYXNoID0gJ3NvbGlkJyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAicmVkIiwgc3ltYm9sID0gJ2RvdCcpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IHB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzJdLF0sIHggPSB+bG9nKG4vMTAsIDEwKSwgeSA9IH5NZW1vcnksIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ01DTUNfc3RhbiBQeXRob24nLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJyZWQiLCBkYXNoID0gJ2Rhc2hkb3QnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJyZWQiLCBzeW1ib2wgPSAnc3F1YXJlJykpICU+JQogIGxheW91dCh0aXRsZSA9ICJNZW1vcnkgdXNhZ2UiLAogICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAibG9nKEIpIiwgdGlja21vZGUgPSAiYXJyYXkiLCB0aWNrdmFscyA9IDA6NiwgdGlja3RleHQgPSBhcy5jaGFyYWN0ZXIoMDo2KSksCiAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJNZW1vcnkgKE1CKSIpLAogICAgICAgICBsZWdlbmQgPSBsaXN0KHRpdGxlID0gIkxlZ2VuZCIsIG9yaWVudGF0aW9uID0gImgiLCB4ID0gMC4zLCB5ID0gLTAuMikpCmBgYAoKCkJvdGggUHl0aG9uIGFuZCBSJ3MgZXhlY3V0aW9uIHRpbWVzIGZvciBNZXRyb3BvbGlzLUhhc3RpbmdzIHNlZW0gdG8gc2NhbGUgbGluZWFybHkgd2l0aCB0aW1lIGFzIGV4cGVjdGVkIGZyb20gdGhlIHRoZW9yZXRpY2FsIGNvbXBsZXhpdHksIGFuZCBSIGdlbmVyYWxseSBzZWVtcyB0byBiZSBmYXN0ZXIgdGhhbiBQeXRob24gcGFydGljdWxhcmx5IGZvciBhIGxhcmdlIG51bWJlciBpdGVyYXRpb25zIGF0ICRCPTEwXjUkLiBJbiB0aGlzIHNpbXBsZSBjYXNlIG9mIGxpbmVhciByZWdyZXNzaW9uLCBydW5uaW5nICQxMF41JCBpdGVyYXRpb25zIGlzIGRlZmluaXRlbHkgdW5jZXNzYXJ5IGFuZCBjb252ZXJnZW5jZSB3YXMgYWNoaWV2ZWQgd2F5IGJlZm9yZSBidXQgdGhpcyBtYXkgYmUgbmVlZGVkIGZvciB2ZXJ5IGNvbXBsZXggbW9kZWxzLiBTdGFuJ3MgZXhlY3V0aW9uIHRpbWUgaXMgdmVyeSBzaW1pbGFyIGluIGJvdGggaW50ZXJmYWNlcyBhbmQgaXQgc2VlbXMgdG8gc2NhbGUgZXZlbiBiZXR0ZXIgdGhhbiBsaW5lYXJseSBmb3IgbGFyZ2UgbnVtYmVyIG9mIGl0ZXJhdGlvbnMuIFNpbmNlIFN0YW4gaGFzIG11Y2ggbG93ZXIgYXV0b2NvcnJlbGF0aW9uIGluIHRoZSBjaGFpbnMgdGhhbiBNZXRyb3BsaXMtSGFzdGluZ3MsIFN0YW4gYWN0dWFsbHkgaGFzIG11Y2ggaGlnaGVyIGVmZmVjdGl2ZSBzYW1wbGUgc2l6ZSB3aGljaCBtYWtlcyBpdCBwZXJmb3JtYW5jZSBxdWl0ZSBpbXByZXNzaXZlLCBhbmQgJDEwXjUkIGl0ZXJhdGlvbnMgaXMgdHlwaWNhbGx5IG1vcmUgdGhhbiBuZWNlc3NhcnkgaW4gU3Rhbi4gV2UgYWxzbyBzZWUgdGhhdCBtZW1vcnkgdXNhZ2UgYmxvd3MgdXAgaW4gUiB3aXRoIDNHQiBvZiBtZW1vcnkgdXNlZCBmb3IgJDEwXjUkIGl0ZXJhdGlvbnMsIGJ1dCBpdCBzdGF5cyBzdXJwcmlzaW5nbHkgbG93IGJlbG93IDEwME1CIGluIFB5dGhvbi4KCgoKIyMgU1ZNCgpJIHdpbGwgbm90IHNheSBtdWNoIGFib3V0IHN1cHBvcnQgdmVjdG9yIG1hY2hpbmVzIChTVk0pIGJlY2F1c2UgSSBhbSBub3QgYXMgZmFtaWxpYXIgd2l0aCB0aGlzIGFsZ29yaXRobSBjb21wYXJlZCB0byBzdGF0aXN0aWNhbCBvbmVzLiBOb25ldGhlbGVzcywgSSB0aG91Z2h0IGl0IHdvdWxkIGJlIGludGVyZXN0aW5nIHRvIGluY2x1ZGUgYSBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobSBzaW5jZSB0aGlzIGlzIG9mdGVuIHRob3VnaHQgdG8gYmUgYSBtYWpvciBzdHJlbmd0aCBvZiBQeXRob24gdGhyb3VnaCBpdHMgbGFyZ2UgZWNvc3lzdGVtIG9mIGxpYnJhcmllcy4gTXkgdW5kZXJzdGFuZGluZyBvZiB0aGUgYWxnb3JpdGhtIHVzaW5nIGEgc2ltcGxlIGZvcm0gb2YgZ3JhZGllbnQgZGVzY2VudCBpcyBhcyBmb2xsb3dzLgoKYGBgcGxhaW50ZXh0CkZ1bmN0aW9uIHN2bShYLCB5LCBlcG9jaHMsIGxlYXJuaW5nX3JhdGUsIEMpCiAgICAvLyBJbml0aWFsaXplIHdlaWdodCBhbmQgYmlhcyBwYXJhbWV0ZXJzCiAgICB3IDwtIHZlY3RvciAwCiAgICBiIDwtIDAKICAgIAogICAgRm9yIGVhY2ggZXBvY2ggZnJvbSAxIHRvIGVwb2NocwogICAgICAgIEZvciBlYWNoIHNhbXBsZSBpIGZyb20gMSB0byBsZW5ndGggb2YgeQogICAgICAgICAgICBEZWNpc2lvbl92YWx1ZSA8LSBYW2ldIGRvdCAodyArIGIpCiAgICAgICAgICAgIAogICAgICAgICAgICAvLyBDaGVjayBpZiBkYXRhIGlzIG9uIHRoZSBjb3JyZWN0IHNpZGUgb2YgdGhlIG1hcmdpbgogICAgICAgICAgICBJZiB5W2ldICogZGVjaXNpb25fdmFsdWUgPCAxIHRoZW4KICAgICAgICAgICAgICAgIC8vIFVwZGF0ZSB3IGFuZCBiIGZvciBpbmNvcnJlY3RseSBjbGFzc2lmaWVkIHNhbXBsZXMKICAgICAgICAgICAgICAgIHcgPC0gdyArIGxlYXJuaW5nX3JhdGUgKiAoeVtpXSAqIFhbaV0gLSAyICogKDEvQykgKiB3KQogICAgICAgICAgICAgICAgYiA8LSBiICsgbGVhcm5pbmdfcmF0ZSAqIHlbaV0KICAgICAgICAgICAgRWxzZQogICAgICAgICAgICAgICAgLy8gVXBkYXRlIHJlZ3VsYXJpemF0aW9uIGZvciBjb3JyZWN0bHkgY2xhc3NpZmllZCBzYW1wbGVzCiAgICAgICAgICAgICAgICB3IDwtIHcgLSBsZWFybmluZ19yYXRlICogKDIgKiAoMS9DKSAqIHcpCiAgICAgICAgICAgIAogICAgUmV0dXJuIHcsIGIKRW5kIEZ1bmN0aW9uCmBgYAoKVGhlIGluaXRpYWxpemF0aW9ucyBhcmUgJE8ocCkkIGFuZCAkTygxKSQsIHRoZSBvdXRlciBsb29wIHJ1bnMgZm9yIGVwb2NocyBpdGVyYXRpb25zIGFuZCB0aGUgaW5uZXIgbG9vcCBydW5zIGZvciAkbiQgaW50ZXJhdGlvbnMsIHRoZSBwcm9kdWN0IFhbaV0gZG90IEMgaXMgJE8ocCkkLCBzbyB0aGUgb3ZlcmFsbCBjb21wbGV4aXR5IGlzICRPKFx0ZXh0e2Vwb2Noc30gXHRpbWVzIG4gXHRpbWVzIHApJC4gSW4gdGhpcyBjYXNlLCBJIGFtIGtlZXBpbmcgZXBvY2hzIGFuZCBwIGZpeGVkIGFuZCBhbSBpbmNyZWFzaW5nIHRoZSBzYW1wbGUgc2l6ZSwgc28gdGhlIGNvbXBsZXhpdHkgaXMgJE8obikkLgoKVGhpcyBpcyBpbXBsZW1lbnRlZCBpbiBQeXRob24gZm9sbG93aW5nIHRoZSBwc2V1ZG9jb2RlIGFuZCB1c2luZyB0aGUgc2tsZWFybiBsaWJyYXJ5IGFzIGZvbGxvd3MuCgpgYGB7cHl0aG9uLCBldmFsID0gRkFMU0V9CiMgU1ZNIGZyb20gYmFzZQpkZWYgc3ZtX2Jhc2UoWCwgeSwgZXBvY2hzPTEwMCwgbGVhcm5pbmdfcmF0ZT0wLjAxLCBDPTEuMCk6CiAgICB3ID0gbnAuemVyb3MoWC5zaGFwZVsxXSkKICAgIGIgPSAwCiAgICBmb3IgZXBvY2ggaW4gcmFuZ2UoZXBvY2hzKToKICAgICAgICBmb3IgaSBpbiByYW5nZShsZW4oeSkpOgogICAgICAgICAgICBpZiB5W2ldICogKG5wLmRvdChYW2ldLCB3KSArIGIpIDwgMToKICAgICAgICAgICAgICAgIHcgKz0gbGVhcm5pbmdfcmF0ZSAqICgoeVtpXSAqIFhbaV0pICsgKC0yICogKDEvQykgKiB3KSkKICAgICAgICAgICAgICAgIGIgKz0gbGVhcm5pbmdfcmF0ZSAqIHlbaV0KICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHcgLT0gbGVhcm5pbmdfcmF0ZSAqICgyICogKDEvQykgKiB3KQogICAgcmV0dXJuIHcsIGIKCiMgc2tsZWFybi1MZWFybiBTVk0KZGVmIHN2bV9za2xlYXJuKFgsIHkpOgogICAgY2xmID0gU1ZDKGtlcm5lbD0nbGluZWFyJywgQz0xLjApCiAgICBjbGYuZml0KFgsIHkpCiAgICByZXR1cm4gY2xmLmNvZWZfWzBdLCBjbGYuaW50ZXJjZXB0X1swXQpgYGAKCgpMaWtld2lzZSwgdGhpcyBpcyBpbXBsZW1lbnRlZCBpbiBSIGZvbGxvd2luZyBhIHNpbWlsYXIgZm9ybSBhbmQgdXNpbmcgYSBwYWNrYWdlIGNhbGxlZCBlMTA3MS4KCmBgYHtyLCBldmFsID0gRkFMU0V9CiMgU2ltcGxlIGxpbmVhciBTVk0gZnJvbSBiYXNlCnN2bV9iYXNlIDwtIGZ1bmN0aW9uKFgsIHksIGVwb2NocyA9IDEwMCwgbGVhcm5pbmdfcmF0ZSA9IDAuMDEsIGxhbWJkYSA9IDAuMDEpIHsKICBuX3NhbXBsZXMgPC0gbnJvdyhYKQogIG5fZmVhdHVyZXMgPC0gbmNvbChYKQogIHdlaWdodHMgPC0gbWF0cml4KDAsIG5yb3cgPSBuX2ZlYXR1cmVzLCBuY29sID0gMSkKICBpbnRlcmNlcHQgPC0gMAogIAogIGZvciAoZXBvY2ggaW4gMTplcG9jaHMpIHsKICAgIGZvciAoaSBpbiAxOm5fc2FtcGxlcykgewogICAgICBpZiAoeVtpXSAqIChjcm9zc3Byb2Qod2VpZ2h0cywgWFtpLCBdKSArIGludGVyY2VwdCkgPCAxKSB7CiAgICAgICAgd2VpZ2h0cyA8LSB3ZWlnaHRzICsgbGVhcm5pbmdfcmF0ZSAqICgoeVtpXSAqIG1hdHJpeChYW2ksIF0sIG5jb2wgPSAxKSkgLSAoMiAqIGxhbWJkYSAqIHdlaWdodHMpKQogICAgICAgIGludGVyY2VwdCA8LSBpbnRlcmNlcHQgKyBsZWFybmluZ19yYXRlICogeVtpXQogICAgICB9IGVsc2UgewogICAgICAgIHdlaWdodHMgPC0gd2VpZ2h0cyAtIGxlYXJuaW5nX3JhdGUgKiAoMiAqIGxhbWJkYSAqIHdlaWdodHMpCiAgICAgIH0KICAgIH0KICB9CiAgbGlzdChjb2VmZmljaWVudHMgPSB3ZWlnaHRzLCBpbnRlcmNlcHQgPSBpbnRlcmNlcHQpCn0KCiMgU1ZNIHVzaW5nIGUxMDcxIHBhY2thZ2UKc3ZtX2UxMDcxIDwtIGZ1bmN0aW9uKFgsIHkpIHsKICBtb2RlbCA8LSBzdm0oeCA9IFgsIHkgPSBhcy5mYWN0b3IoeSksIGtlcm5lbCA9ICJsaW5lYXIiKQogIGxpc3QoY29lZmZpY2llbnRzID0gdChhcy52ZWN0b3IobW9kZWwkY29lZnMpICUqJSBtb2RlbCRTViksIGludGVyY2VwdCA9IC1tb2RlbCRyaG8pCn0KYGBgCgoKCmBgYHtyIGVjaG89RkFMU0V9CmFsZ29zIDwtIGMoInN2bV9iYXNlIiwgInN2bV9wYWNrYWdlIikKbl92YWx1ZXMgPC0gc2VxKDEsIDMsIGJ5ID0gMC4xKQpPX24gPC0gMTBebl92YWx1ZXMvMTBeMgpPX25fcHl0aG9uXzEgPC0gT19uKnB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzFdLF0kVGltZVszXQpPX25fUl8xIDwtIE9fbipSW1IkQWxnb3JpdGhtID09IGFsZ29zWzFdLF0kVGltZVszXQpPX25fcHl0aG9uXzIgPC0gT19uKnB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzJdLF0kVGltZVszXQpPX25fUl8yIDwtIE9fbipSW1IkQWxnb3JpdGhtID09IGFsZ29zWzJdLF0kVGltZVszXQpwbG90X2x5KCkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBSW1IkQWxnb3JpdGhtID09IGFsZ29zWzFdLF0sIHggPSB+bG9nKHNxcnQobiksIDEwKSwgeSA9IH5UaW1lLCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdTVk1fc2NyYXRjaCBSJywKICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIGRhc2ggPSAnc29saWQnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJibHVlIiwgc3ltYm9sID0gJ2NpcmNsZScpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IFJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMl0sXSwgeCA9IH5sb2coc3FydChuKSwgMTApLCB5ID0gflRpbWUsIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ1NWTV9lMTA3MSBSJywKICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIGRhc2ggPSAnZGFzaGRvdCcpLAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBzeW1ib2wgPSAnc3F1YXJlJykpICU+JQogIGFkZF90cmFjZShkYXRhID0gcHl0aG9uW3B5dGhvbiRBbGdvcml0aG0gPT0gYWxnb3NbMV0sXSwgeCA9IH5sb2coc3FydChuKSwgMTApLCB5ID0gflRpbWUsIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ1NWTV9zY3JhdGNoIFB5dGhvbicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gInJlZCIsIGRhc2ggPSAnc29saWQnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJyZWQiLCBzeW1ib2wgPSAnZG90JykpICU+JQogIGFkZF90cmFjZShkYXRhID0gcHl0aG9uW3B5dGhvbiRBbGdvcml0aG0gPT0gYWxnb3NbMl0sXSwgeCA9IH5sb2coc3FydChuKSwgMTApLCB5ID0gflRpbWUsIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ1NWTV9za2xlYXJuIFB5dGhvbicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gInJlZCIsIGRhc2ggPSAnZGFzaGRvdCcpLAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gInJlZCIsIHN5bWJvbCA9ICdzcXVhcmUnKSkgJT4lCiAgYWRkX3RyYWNlKHggPSBuX3ZhbHVlcywgeSA9IE9fbl9SXzEsIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMnLAogICAgICAgICAgICBuYW1lID0gJ08obiknLCBsaW5lID0gbGlzdChjb2xvciA9ICdibGFjaycsIGRhc2ggPSAnZGFzaCcpKSAlPiUKICBhZGRfdHJhY2UoeCA9IG5fdmFsdWVzLCB5ID0gT19uX3B5dGhvbl8xLCB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzJywKICAgICAgICAgICAgbmFtZSA9ICdPKG4pJywgbGluZSA9IGxpc3QoY29sb3IgPSAnYmxhY2snLCBkYXNoID0gJ2Rhc2gnKSwgc2hvd2xlZ2VuZD0gRkFMU0UpICU+JQogIGxheW91dCh0aXRsZSA9ICJFeGVjdXRpb24gVGltZSIsCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJsb2cobikiLCB0aWNrbW9kZSA9ICJhcnJheSIsIHRpY2t2YWxzID0gMDo2LCB0aWNrdGV4dCA9IGFzLmNoYXJhY3RlcigwOjYpKSwKICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIlRpbWUgKHMpIiksCiAgICAgICAgIGxlZ2VuZCA9IGxpc3QodGl0bGUgPSAiTGVnZW5kIiwgb3JpZW50YXRpb24gPSAiaCIsIHggPSAwLjMsIHkgPSAtMC4yKSkKcGxvdF9seSgpICU+JQogIGFkZF90cmFjZShkYXRhID0gUltSJEFsZ29yaXRobSA9PSBhbGdvc1sxXSxdLCB4ID0gfmxvZyhzcXJ0KG4pLCAxMCksIHkgPSB+TWVtb3J5LCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdTVk1fc2NyYXRjaCBSJywKICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIGRhc2ggPSAnc29saWQnKSwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJibHVlIiwgc3ltYm9sID0gJ2NpcmNsZScpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IFJbUiRBbGdvcml0aG0gPT0gYWxnb3NbMl0sXSwgeCA9IH5sb2coc3FydChuKSwgMTApLCB5ID0gfk1lbW9yeSwgCiAgICAgICAgICAgIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMrbWFya2VycycsIG5hbWUgPSAnU1ZNX2UxMDcxIFInLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJibHVlIiwgZGFzaCA9ICdkYXNoZG90JyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIHN5bWJvbCA9ICdzcXVhcmUnKSkgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBweXRob25bcHl0aG9uJEFsZ29yaXRobSA9PSBhbGdvc1sxXSxdLCB4ID0gfmxvZyhzcXJ0KG4pLCAxMCksIHkgPSB+TWVtb3J5LCAKICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbmFtZSA9ICdTVk1fc2NyYXRjaCBQeXRob24nLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJyZWQiLCBkYXNoID0gJ3NvbGlkJyksCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAicmVkIiwgc3ltYm9sID0gJ2RvdCcpKSAlPiUKICBhZGRfdHJhY2UoZGF0YSA9IHB5dGhvbltweXRob24kQWxnb3JpdGhtID09IGFsZ29zWzJdLF0sIHggPSB+bG9nKHNxcnQobiksIDEwKSwgeSA9IH5NZW1vcnksIAogICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLCBuYW1lID0gJ1NWTV9za2xlYXJuIFB5dGhvbicsCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gInJlZCIsIGRhc2ggPSAnZGFzaGRvdCcpLAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gInJlZCIsIHN5bWJvbCA9ICdzcXVhcmUnKSkgJT4lCiAgbGF5b3V0KHRpdGxlID0gIk1lbW9yeSB1c2FnZSIsCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJsb2cobikiLCB0aWNrbW9kZSA9ICJhcnJheSIsIHRpY2t2YWxzID0gMDo2LCB0aWNrdGV4dCA9IGFzLmNoYXJhY3RlcigwOjYpKSwKICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIk1lbW9yeSAoTUIpIiksCiAgICAgICAgIGxlZ2VuZCA9IGxpc3QodGl0bGUgPSAiTGVnZW5kIiwgb3JpZW50YXRpb24gPSAiaCIsIHggPSAwLjMsIHkgPSAtMC4yKSkKYGBgCgpCb3RoIHRoZSBSIGFuZCBQeXRob24gaW1wbGVtZW50YXRpb25zIGZyb20gc2NyYXRjaCBzZWVtIHRvIHNjYWxlIHJvdWdobHkgbGluZWFybHkgd2l0aCBzYW1wbGUgc2l6ZSBhbmQgUiBpcyBnZW5lcmFsbHkgZmFzdGVyIHRoYW4gUHl0aG9uLCBidXQgdGhlIHBhY2thZ2UgaW1wbGVtZW50YXRpb25zIG9mIFNWTSBhcmUgY29uc2lkZXJhYmx5IGZhc3Rlci4gTWVtb3J5IHVzYWdlIGlzIGFnYWluIHN0cmFuZ2VseSB2ZXJ5IGxvdyBpbiBSIGFuZCB0aGVyZSBpcyBubyBkaXNjZXJuYWJsZSB0cmVuZC4KCgojIERpc2N1c3Npb24geyNkaXNjdXNzaW9ufSAKCgojIyBFeGVjdXRpb24gdGltZQoKUiBnZW5lcmFsbHkgaGFkIGJldHRlciBleGVjdXRpb24gdGltZXMgZm9yIHNtYWxsZXIgdG8gbWVkaXVtLXNpemVkIGRhdGFzZXRzIGFjcm9zcyBhbGwgdHlwZXMgb2Ygb3BlcmF0aW9ucyBJIHRlc3RlZC4gTW9yZSBzcGVjaWZpY2FsbHksIFIgaGFkIGZhc3RlciBsb29wcyBmb3IgYWxsIHNhbXBsZSBzaXplcywgZmFzdGVyIHZlY3Rvcml6ZWQgb3BlcmF0aW9ucyBmb3Igc21hbGwtbWVkaXVtIHNhbXBsZSBzaXplcywgYW5kIGZhc3RlciBtYXRyaXggb3BlcmF0aW9ucyBmb3Igc21hbGwtbWVkaXVtIHNhbXBsZSBzaXplcy4gQ29uc2lzdGVudCB3aXRoIHRoZXNlIHJlc3VsdHMsIGxpbmVhciByZWdyZXNzaW9uIHdoaWNoIGlzIGp1c3QgaW1wbGVtZW50ZWQgYXMgbWF0cml4IG9wZXJhdGlvbnMgd2FzIGZhc3RlciBmb3Igc21hbGwtbWVkaXVtIHNhbXBsZSBzaXplcywgYW5kIHRoZSBib290c3RyYXAsIE1DTUMsIGFuZCBTVk0gd2hpY2ggYXJlIGxvb3AgYWxnb3JpdGhtcyB3ZXJlIGZhc3RlciBvdmVyYWxsIGluIFIuIFRoaXMgbWF5IGJlIGR1ZSB0byBSJ3MgZWZmaWNpZW50IGhhbmRsaW5nIG9mIHZlY3Rvcml6ZWQgb3BlcmF0aW9ucyBhbmQgaXRzIG9wdGltaXphdGlvbiBmb3Igb3BlcmF0aW9ucyBjb21tb25seSB1c2VkIGluIHN0YXRpc3RpY3MgYW5kIGRhdGEgbWFuaXB1bGF0aW9uLiAKCkhvd2V2ZXIsIFB5dGhvbiBleGhpYml0ZWQgYmV0dGVyIHBlcmZvcm1hbmNlIHdpdGggbGFyZ2UgZGF0YXNldHMsIHBhcnRpY3VsYXJseSBpbiB2ZWN0b3JpemVkIG9wZXJhdGlvbnMgYW5kIG1hdHJpeCBtYW5pcHVsYXRpb25zLiBMaWtld2lzZSwgbGluZWFyIHJlZ3Jlc3Npb24gd2FzIGZhc3RlciBpbiBQeXRob24gZm9yIGxhcmdlIHNhbXBsZSBzaXplcy4gVGhpcyBpcyBwb3NzaWJseSBkdWUgdG8gaXRzIGVmZmljaWVudCBsaWJyYXJpZXMgbGlrZSBOdW1QeSwgd2hpY2ggYXJlIGRlc2lnbmVkIHRvIGhhbmRsZSBsYXJnZSBzY2FsZSBkYXRhIGVmZmljaWVudGx5LgoKIyMgTWVtb3J5IHVzYWdlCgpJbiBtb3N0IGNhc2VzLCBJIGRvIG5vdCB0aGluayB0aGF0IG1lbW9yeSB1c2FnZSB3YXMgd2VsbC1tZWFzdXJlZCBpbiBSLCBzbyBJIHdpbGwgbm90IG1ha2Ugb3ZlcmFsbCBzdGF0ZW1lbnRzIGFib3V0IG1lbW9yeSBtYW5hZ2VtZW50LiBIb3dldmVyLCB0aGUgYm9vdHN0cmFwIGFuZCBNQ01DIHdlcmUgY29zdGx5IGZvciBtZW1vcnkgaW4gYm90aCBsYW5ndWFnZXMuIEluIGFkZGl0aW9uLCBtZW1vcnkgbWFuYWdlbWVudCBpbiBQeXRob24gc2VlbWVkIHRvIGJlIGZvciB0aGUgbW9zdCBwYXJ0IGFnbm9zdGljIHRvIHRoZSB0eXBlIG9mIG9wZXJhdGlvbnMgZXhlY3V0ZWQsIHVzaW5nIHNpbWlsYXIgbWVtb3J5IGZvciBhIHNpbXBsZSBzdW0gbG9vcCBhbmQgZm9yIHRoZSBzdXBwb3J0IHZlY3RvciBtYWNoaW5lIGFsZ29yaXRobS4gIEluIGFkZGl0aW9uLCBQeXRob24gZGlkIHNlZW0gdG8gcmVxdWVzdCBtdWNoIG1vcmUgb2YgbXkgY29tcHV0ZXIncyBtZW1vcnkgdGhhbiBSLCBhbmQgSSBoYWQgdG8gbGVhdmUgaXQgcnVubmluZyBvdmVybmlnaHQgdG8gYXZvaWQgcnVubmluZyBvdGhlciBvcGVyYXRpb25zIHNpbXVsdGFuZW91c2x5IG9uIG15IGNvbXB1dGVyLCBidXQgSSBkbyBub3QgdGhpbmsgdGhpcyBpcyB3ZWxsLXF1YW50aWZpZWQuIEluIGFkZGl0aW9uLCBib3RoIGxhbmd1YWdlcyBhcmUgZ2FyYmFnZS1jb2xsZWN0ZWQgbGFuZ3VhZ2UsIG1lYW5pbmcgdGhhdCB0aGV5IGF1dG9tYXRpY2FsbHkgcmVsZWFzZSBtZW1vcnkgd2hlbiB0aGV5IGNoZWNrIHRoYXQgdGhlIG9iamVjdHMgYXJlIG5vdCB1c2VkLCBidXQgbWVtb3J5IGF0IGFueSBnaXZlbiB0aW1lIGlzIG5vdCBuZWNlc3NhcmlseSByZXByZXNlbnRhdGl2ZSBvZiBjb21wdXRhdGlvbmFsIGNvbXBsZXhpdHkuCgpFdmVuIGlmIHRoZSBtZW1vcnkgdXNhZ2Ugb2YgYW4gb3BlcmF0aW9uIHdhcyB3ZWxsLW1lYXN1cmVkIGluIFIgYW5kIFB5dGhvbiwgaXQgaXMgbm90IGNsZWFyIGlmIGl0IGlzIGRlc2lyYWJsZSB0byBoYXZlIGxvdyBvciBoaWdoIG1lbW9yeSB1c2FnZSwgc2luY2UgbG93IG1lbW9yeSB1c2FnZSBjb3VsZCBiZSBkdWUgdG8gYW4gZWZmaWNpZW50IGltcGxlbWVudGF0aW9uIG9yIHJhdGhlciBiZSBkdWUgdG8gcG9vciBtZW1vcnkgYXR0cmlidXRpb24gd2hpY2ggZW5kcyB1cCBzbG93aW5nIGRvd24gdGhlIHByb2dyYW0uIEhlbmNlLCB0aGVyZSBpcyBhIHRyYWRlb2ZmIGJldHdlZW4gZXhlY3V0aW9uIHRpbWUgYW5kIG1lbW9yeSBzdG9yYWdlLgoKIyMgT3RoZXIgY29uc2lkZXJhdGlvbnMKSW4gdGVybXMgb2YgY29kZSB3cml0aW5nLCBJIHBlcnNvbmFsbHkgcHJlZmVyIFIgZm9yIG1vc3QgZGF0YSBzY2llbmNlIHByb2plY3RzIGJlY2F1c2UgSSBjYW4gdmVyeSBxdWlja2x5IGFuYWx6eWUgZGF0YSBpbiBhIGNvdXBsZSBsaW5lcyB3aXRob3V0IHRoaW5raW5nIG11Y2ggYWJvdXQgdGhlIHN5bnRheC4gQWxzbywgSSBmaW5kIHRoZSBzeW50YXggb2YgZHBseXIgdmVyeSBjbGVhciBmb3IgZGF0YSBtYW5pcHVsYXRpb24sIGFuZCBJIHRoaW5rIHRoYXQgdGhlIGhpZ2ggcXVhbGl0eSB2aXN1YWxpemF0aW9uIG9mIGdncGxvdDIgaXMgY2xlYXJseSB1bm1hdGNoZWQgYnkgbWF0cGxvdGxpYiBvciBhbnkgb3RoZXIgbGlicmFyeSBpbiBweXRob24uIEhvd2V2ZXIsIGlmIEkgaGF2ZSB0byBpbXBsZW1lbnQgYSBtb3JlIGNvbXBsZXggb3BlcmF0aW9uLCBJIG9mdGVuIGZpbmQgUidzIHN5bnRheCBmcnVzdHJhdGluZyBzaW5jZSBJIGNvbnN0YW50bHkgaGF2ZSB0byBsb29rIHVwIG9ic2N1cmUgbm90YXRpb25zLCBhbmQgdGhlIGV4ZWN1dGlvbiBpcyBjZXJ0YWlubHkgbXVjaCBzbG93ZXIgdGhhbiBhIGxvdy1sZXZlbCBsYW5ndWFnZSBsaWtlIEMuIE9uIHRoZSBvdGhlciBoYW5kLCBQeXRob24gaXMgbXVjaCBiZXR0ZXItc3VwcG9ydGVkIGZvciBtYWNoaW5lIGxlYXJuaW5nIGFuZCBmb3IgZ2VuZXJhbCBwdXJwb3NlIHByb2dyYW1taW5nLgoKSW4gdGhpcyBwcm9qZWN0LCBhIGNoYWxsZW5nZSBmb3IgbWUgd2FzIHRvIHN0b3JlIHRoZSBzaW11bGF0ZWQgbWFubmVyIHRoYXQgY2FuIGJlIGVhc2lseSByZWFkIGluIGJvdGggUHl0aG9uIGFuZCBSLiBJZiBJIHdhcyB1c2luZyBqdXN0IFIgSSB3b3VsZCB1c2UgYSAucmRzIGZpbGUgY29udGFpbmluZyBhbGwgdGhlIGRhdGEgaW4gYW4gb2JqZWN0IHdpdGggY29tcGxleCBzdHJ1Y3R1cmUgdGhhdCBjYW4gYmUgZWFzaWx5IGltcG9ydGVkLiBIb3dldmVyLCB0byBiZSByZWFkIGluIFB5dGhvbiBJIGhhZCB0byBzYXZlIHRoZSBkYXRhIGluIHNldmVyYWwgLmNzdiBmaWxlcyB3aGljaCB3ZXJlIHJlYWQgd2l0aCBhbiBhd2t3YXJkIHN0cnVjdHVyZSBpbiBQeXRob24gYW5kIEkgaGFkIHRvIGluY2x1ZGUgYSB3b3JyaXNvbWUgZXJyb3JzPSdjb2VyY2UnIGxpbmUgd2hpY2ggc29sdmVkIHRoZSBwcm9ibGVtIHRoYXQgbXkgZmlsZSBpbmNsdWRlZCBib3RoIHN0cmluZ3MgYW5kIG51bWVyaWNzLiBUaGlzIGlzIG1haW5seSBteSBmYXVsdCBiZWNhdXNlIEkgZG8gbm90IGtub3cgaG93IHRvIHBvZ3JhbSBpbiBQeXRob24gd2VsbCwgYnV0IFIgbWFrZXMgaXQgKGRhbmdlcm91c2x5KSBlYXN5IHRvIHVzZSBkaWZmZXJlbnQgb2JqZWN0cyB3aXRob3V0IHRoaW5raW5nIGFib3V0IHN0cnVjdHVyZSB0b28gbXVjaC4gSSBmaW5kIHRoaXMgdG8gYmUgYSBncmVhdCBhZHZhbnRhZ2UuIEZvciB0aGUgcHVycG9zZSBvZiB0aGlzIHByb2plY3QsIEkgZGlkIG5vdCByZWFsbHkgY2FyZSBtdWNoIGFib3V0IGltcG9ydGluZyB0aGUgZGF0YSBzbW9vdGhseSBzaW5jZSBJIHdhcyBmb2N1c2VkIG9uIG1ha2luZyB0aGUgYWxnb3JpdGhtcyBjb21wYXJhYmxlLiBBbiBpbnRlcmVzdGluZyBhc3BlY3QgdGhhdCBJIGRpZCBub3QgdGVzdCB3YXMgdGhlIHNwZWVkIG9mIHJlYWRpbmcgZGF0YSwgd2hpY2ggSSBjbGVhcmx5IGRvbid0IGtub3cgZW5vdWdoIGFib3V0IHRvIHRlc3QgZmFpcmx5LCBidXQgaXQgY2FuIGJlIGEgbWFqb3IgY29uc2lkZXJhdGlvbiBmb3IgbGFyZ2UgZGF0YXNldHMuIEFub3RoZXIgdGhpbmcgSSBkaWQgbm90IGNvbnNpZGVyIHdhcyBwYXJhbGxlbCBjb21wdXRpbmcsIHdoaWNoIGNhbiBiZSBpbXBsZW1lbnRlZCBpbiBib3RoIFIgYW5kIFB5dGhvbiBhbmQgY2FuIHNpZ25pZmljYW50bHkgc3BlZWQgdXAgY29tcHV0YXRpb25zLiBUaGVyZSBhcmUgYWxzbyBtYW55IG90aGVyIHByb2dyYW1taW5nIGxhbmd1YWdlcyB0aGFuIFB5dGhvbiBhbmQgUiB0aGF0IGNhbiBiZSB1c2VkIGluIGRhdGEgc2NpZW5jZSwgc3VjaCBhcyBKdWxpYSBvciBvbiB0aGUgb3RoZXIgZW5kIG9mIGFic3RyYWN0aW9uLCBDL0MrKy4KCiMjIEludGVyYWN0aXZlIHZpc3VhbGl6YXRpb24KSSB1c2VkIHBsb3RseSB0byBtYWtlIHRoZSBpbnRlcmFjdGl2ZSBwbG90cyBpbiB0aGlzIHBhZ2UsIGFuZCBpdCBpcyB0aGUgZmlyc3QgdGltZSBJIHVzZWQgaW50ZXJhY3RpdmUgdmlzdWFsaXphdGlvbi4gSSBkaWQgbm90IHJlYWxseSBsaWtlIHRoaXMgZXhwZXJpZW5jZSBiZWNhdXNlIEkgdGhpbmsgdGhlIHBsb3RzIG1vc3RseSBlbmQgdXAgZGlzdHJhY3RpbmcgdGhlIHVzZXIgd2l0aG91dCBjb252ZXlpbmcgbXVjaCBtb3JlIGluZm9ybWF0aW9uLiBJIGZpbmQgZXhwbG9yYXRvcnkgZGF0YSB2aXN1YWxpemF0aW9uIG11Y2ggZWFzaWVyIGFuZCBiZWF1dGlmdWwgaW4gZ2dwbG90MiBvciBldmVuIGJhc2UgUi4gSG93ZXZlciwgaXQgYWxzbyBzZWVtcyBtb3JlIGludGVyZXN0aW5nIGFuZCBleWUtY2F0Y2hpbmcgdGhhbiBhIGJsYWNrLWFuZC13aGl0ZSBwbG90IHlvdSB3b3VsZCByZWFkIGluIGEgcGFwZXIsIHBhcnRpY3VsYXJseSBmb3IgYmxvZyBwb3N0IGZvcm1hdCBsaWtlIHRoaXMuCgpJIGFsc28gY2hvc2UgdG8gcGxvdCB0aGUgZGF0YSBvbiBhIGxvZyBzY2FsZSwgd2hpY2ggaXMgZGlmZmljdWx0IHRvIGludGVycHJldC4gRm9yIGluc3RhbmNlLCB3ZSBzZWUgdGhhdCBsaW5lYXIgZnVuY3Rpb25zIGJlY29tZSB2ZXJ5IGRpc3RvcnRlZC4gSG93ZXZlciwgSSB0aG91Z2h0IHRoaXMgY2hvaWNlIHdhcyByZWFzb25hYmxlIHRvIGhpZ2hsaWdodCB0aGUgZGlmZmVyZW50IG5lZWRzIG9mIGRhdGEgc2NpZW5jZSBmcm9tIHNtYWxsIHNhbXBsZXMsIHdoaWNoIGlzIG15IG1haW4gYXJlYSBvZiBpbnRlcmVzdCwgdG8gYmlnIGRhdGEuIEFzIGEgc3RhdGlzdGljaWFuLCBJIGFsc28gd291bGQgaGF2ZSB3aXNoZWQgdG8gY29udmV5IHVuY2VydGFpbnR5IGluIHRoZXNlIGVzdGltYXRlcy4gSSB1c2VkIDEwIGl0ZXJhdGlvbnMgb2YgZWFjaCBhbGdvcml0aG0gYW5kIGNvbXB1dGVkIHRoZSBtZWRpYW4sIGJ1dCBpdCBtYXkgaGF2ZSBiZWVuIGludGVyZXN0aW5nIHRvIGZpbmQgYSB3YXkgdG8gaW5jbHVkZSB1bmNlcnRhaW50eSBvbiB0aGUgcGxvdHMsIGFsdGhvdWdoIGl0IHdvdWxkIGhhdmUgbWFkZSB0aGUgcGxvdHMgbW9yZSBjbHV0dGVyZWQgdGhhbiB0aGV5IGFscmVhZHkgYXJlLgoKVGhpcyB3YXMgYWxzbyBteSBmaXJzdCB0aW1lIGNyZWF0aW5nIGEgd2Vic2l0ZSwgd2hpY2ggSSBmb3JrZWQgZnJvbSBtbWlzdGFrZXMvbWluaW1hbC1taXN0YWtlcy4gSSBsaWtlZCB0aGlzIGZvcm1hdCBvZiB1c2luZyBtYXJrZG93biBub3RlYm9vaywgd2hpY2ggYWxsb3dlZCB0byBlYXNpbHkgaW5jbHVkZSB0ZXh0LCBjb2RlLCBhbmQgcGxvdHMgZnJvbSBkaWZmZXJlbnQgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2VzIGluIGFuIGFlc3RoZXRpY2FsbHkgcGxlYXNpbmcgZm9ybWF0LiBIb3dldmVyLCB0aGUgZWFzaW5lc3Mgb2Ygd3JpdGluZyBpbiBhIG1hcmtkb3duIGZpbGUgYXMgb3Bwb3NlZCB0byBIVE1MIGFuZCBDU1MgZmlsZXMgY29tZXMgYXQgdGhlIGNvc3Qgb2YgY3VzdG9taXphdGlvbi4KCiMjIElzIFIgb3IgUHl0aG9uIGJldHRlcj8KQ2xlYXJseSwgY29tcHV0YXRpb25hbCBwZXJmb3JtYW5jZSBpcyBoaWdobHkgZGVwZW5kZW50IG9uIHRoZSBzaXR1YXRpb24sIGluY2x1ZGluZyB0aGUgZGF0YSBkaW1lbnNpb24sIHRoZSBhbGdvcml0aG0gb2YgaW50ZXJlc3QsIHRoZSBlYXNpbmVzcyBvZiBvcHRpbWl6aW5nIGNvZGUsIGFuZCB0aGUgbWFjaGluZSB1c2VkIHRvIGNvbXB1dGUgaXQuIFRoZXJlZm9yZSwgSSBjYW5ub3QgZXZlbiBhbnN3ZXIgdGhlIHF1ZXN0aW9uIG9mIHdoZXRoZXIgUiBvciBQeXRob24gaXMgZmFzdGVyIGZvciBteSB1c2VzIGFuZCBteSBjb21wdXRlciwgbGV0IGFsb25lIGZvciBhbnlvbmUgZWxzZS4gSG93ZXZlciwgaW4gbW9zdCBvZiBteSBldmVyeWRheSBkYXRhIHNjaWVuY2Ugb3BlcmF0aW9ucywgbXkgc2FtcGxlIHNpemVzIGFyZSBiZWxvdyAkMTBeNSQsIGFuZCB0aGlzIHNpbXVsYXRpb24gc2hvd3MgbWUgdGhhdCBpbiB0aGF0IGNvbnRleHQgUiBrZWVwcyB1cCB3aXRoIFB5dGhvbiBhbmQsIHRvIG15IHN1cnByaXNlLCBpcyBvZnRlbiBiZXR0ZXIuIEluIHNvbWUgY2FzZXMsIHN1Y2ggYXMgdmVyeSBsYXJnZSBzYW1wbGUgc2l6ZXMgb3IgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIHdpdGggd2VsbC1pbXBsZW1lbnRlZCBsaWJyYXJpZXMsIFB5dGhvbiBtYXkgYmUgc2lnbmlmaWNhbnRseSBmYXN0ZXIuIE92ZXJhbGwsIGl0IGRvZXMgc2VlbSB0aGF0IFIgd2FzIG11Y2ggZmFzdGVyIGZvciBtZSwgd2hpY2ggaXMgcGFydGljdWxhcmx5IGNsZWFyIHRocm91Z2ggdGhlIGZhY3QgdGhhdCB0aGUgZW50aXJlIHNjcmlwdCB3YXMgcnVuIGluIDkgbWludXRlcyBpbiBSIGFuZCBpbiAxMjYgbWludXRlcyBpbiBQeXRob24uCg==